เธรดของ Windows: _beginthread vs _beginthreadex vs CreateThread C ++


134

สิ่งที่เป็นวิธีที่ดีกว่าที่จะเริ่มต้นด้าย_beginthread, _beginthreadxหรือCreateThread?

ฉันพยายามที่จะตรวจสอบสิ่งที่เป็นข้อดี / ข้อเสียของ_beginthread, และ_beginthreadex CreateThreadฟังก์ชั่นทั้งหมดนี้ส่งคืนตัวจัดการเธรดไปยังเธรดที่สร้างขึ้นใหม่ฉันรู้อยู่แล้วว่า CreateThread ให้ข้อมูลเพิ่มเติมเล็กน้อยเมื่อเกิดข้อผิดพลาด (สามารถตรวจสอบได้โดยการโทรGetLastError) ... แต่มีสิ่งใดบ้างที่ฉันควรพิจารณาเมื่อฉัน m โดยใช้ฟังก์ชันเหล่านี้หรือไม่

ฉันกำลังทำงานกับแอปพลิเคชัน windows ดังนั้นความเข้ากันได้ข้ามแพลตฟอร์มจึงไม่เป็นปัญหา

ฉันได้อ่านเอกสาร msdn แล้วและฉันก็ไม่เข้าใจเช่นทำไมใคร ๆ ถึงตัดสินใจใช้ _beginthread แทน CreateThread หรือในทางกลับกัน

ไชโย!

อัปเดต: โอเคขอบคุณสำหรับข้อมูลทั้งหมดฉันได้อ่านในสถานที่สองสามแห่งที่ฉันไม่สามารถโทรหาได้WaitForSingleObject()หากฉันใช้_beginthread()แต่ถ้าฉันโทร_endthread()ในเธรดจะไม่ได้ผล? มีข้อตกลงอะไรบ้าง?


2
นี่คือการวิเคราะห์สิ่งที่ _beginthreadex () ทำสำหรับโปรแกรมเมอร์ C / C ++ที่ฉันพบจากลิงค์ในเว็บไซต์ของ Eli Bendersky นี่มาจากคำถาม & คำตอบว่าจะใช้ CreateThread () หรือไม่ microsoft.com/msj/0799/win32/win320799.aspx
Richard Chambers

คำตอบ:


97

CreateThread() เป็นการเรียกใช้ Win32 API แบบดิบสำหรับการสร้างเธรดการควบคุมอื่นที่ระดับเคอร์เนล

_beginthread()& _beginthreadex()คือ C รันไทม์ไลบรารีเรียกที่เรียกCreateThread()เบื้องหลัง เมื่อCreateThread()กลับมาแล้วให้_beginthread/ex()ดูแลการทำบัญชีเพิ่มเติมเพื่อให้ไลบรารีรันไทม์ C ใช้งานได้และสอดคล้องกันในเธรดใหม่

ใน C ++ คุณควรใช้อย่างแน่นอน_beginthreadex()เว้นแต่คุณจะไม่ได้เชื่อมโยงกับไลบรารีรันไทม์ของ C เลย (aka MSVCRT * .dll / .lib)


40
สิ่งนี้ไม่เป็นความจริงอย่างที่เคยเป็นอีกต่อไป CRT จะทำงานได้อย่างถูกต้องในเธรดที่สร้างโดย CreateThread () ยกเว้นฟังก์ชัน he signal () จะมีการรั่วไหลของหน่วยความจำขนาดเล็ก (~ 80 ไบต์) สำหรับแต่ละเธรดที่สร้างด้วย CreateThread () ที่ใช้ CRT แต่จะทำงานได้อย่างถูกต้อง ดูข้อมูลเพิ่มเติม: support.microsoft.com/default.aspx/kb/104641
John Dibling

1
@ จอห์น: จริงๆแล้วบั๊กนั้นใช้ได้กับMSVC ++ 6.0 เท่านั้น
bobobobo

5
@bobobobo: คำถามที่ดี ฉันสามารถคาดเดาได้ว่าเดิมที MS ตั้งใจ_beginให้รูทีนเป็นการโทรภายในและCreateThreadควรเป็นฟังก์ชัน API ที่ทุกคนเรียกใช้ คำอธิบายที่เป็นไปได้อีกประการหนึ่งคือ MS มีประวัติอันยาวนานและมีชื่อเสียงในการเพิกเฉยต่อมาตรฐานและการตัดสินใจที่ไม่ดีเกี่ยวกับการตั้งชื่อสิ่งต่างๆ
John Dibling

15
_beginฟังก์ชั่นเริ่มต้นด้วยการขีดเส้นใต้เพราะไมโครซอฟท์เริ่มที่จะปฏิบัติตามมาตรฐานอย่างใกล้ชิด ในรันไทม์ C ชื่อที่มีขีดล่างถูกสงวนไว้สำหรับการนำไปใช้งาน (และการนำไปใช้งานสามารถจัดทำเป็นเอกสารสำหรับการใช้งานของผู้ใช้ปลายทางเช่นเดียวกับสิ่งเหล่านี้) beginthreadex()เป็นชื่อที่อนุญาตให้ผู้ใช้ใช้ หากรันไทม์ C ใช้มันอาจขัดแย้งกับสัญลักษณ์ผู้ใช้ปลายทางที่ผู้ใช้มีสิทธิ์โดยชอบด้วยกฎหมายที่จะสามารถคาดหวังว่าจะใช้ได้ โปรดทราบว่า Win32 API ไม่ได้เป็นส่วนหนึ่งของรันไทม์ C และใช้เนมสเปซของผู้ใช้
Michael Burr

2
@Lothar: มีมีความแตกต่างระหว่างการเรียก API Win32 CreateThreadและบริการโทรจอ CRT _beginthread/exและเมื่อโทร CRT _beginthread/exในหัวข้อนั้นมันควรได้รับการสร้างขึ้นด้วย อาจไม่มีการรั่วไหลของหน่วยความจำอีกต่อไปหากคุณไม่ทำเช่นนั้น แต่แน่นอนว่าคุณจะไม่ได้รับสภาพแวดล้อมทศนิยมของคุณเริ่มต้นอย่างถูกต้องเมื่อโทรCreateThreadเช่น ยังมีอีกมาก : "หากเธรดที่สร้างโดยใช้CreateThreadเรียก CRT CRT อาจยุติกระบวนการในสภาวะที่มีหน่วยความจำเหลือน้อย"
IInspectable

38

มีความแตกต่างหลายครั้งระหว่างมีและ _beginthread() ถูกสร้างขึ้นเพื่อทำหน้าที่เหมือนมากขึ้น(ทั้งในพารามิเตอร์และลักษณะการทำงาน)_beginthreadex()_beginthreadex()CreateThread()

ตามที่Drew Hallกล่าวถึงหากคุณใช้รันไทม์ C / C ++ คุณต้องใช้_beginthread()/ _beginthreadex()แทนCreateThread()เพื่อให้รันไทม์มีโอกาสเริ่มต้นเธรดของตัวเอง (การตั้งค่าพื้นที่จัดเก็บเธรดในเครื่อง ฯลฯ )

ในทางปฏิบัติหมายความว่าCreateThread()โค้ดของคุณไม่ควรใช้โดยตรง

เอกสาร MSDN สำหรับ_beginthread()/ _beginthreadex()มีรายละเอียดเล็กน้อยเกี่ยวกับความแตกต่าง - หนึ่งในสิ่งที่สำคัญกว่านั้นคือเนื่องจากที่จับเธรดสำหรับเธรดที่สร้างโดย_beginthread()จะถูกปิดโดยอัตโนมัติโดย CRT เมื่อเธรดออก "หากเธรดที่สร้างโดย _beginthread ออก อย่างรวดเร็วที่จับส่งกลับไปยังผู้เรียกของ _beginthread อาจไม่ถูกต้องหรือแย่กว่านั้นให้ชี้ไปที่เธรดอื่น "

นี่คือความคิดเห็นสำหรับ_beginthreadex()แหล่งที่มา CRT กล่าว:

Differences between _beginthread/_endthread and the "ex" versions:

1)  _beginthreadex takes the 3 extra parameters to CreateThread
  which are lacking in _beginthread():
    A) security descriptor for the new thread
    B) initial thread state (running/asleep)
    C) pointer to return ID of newly created thread

2)  The routine passed to _beginthread() must be __cdecl and has
  no return code, but the routine passed to _beginthreadex()
  must be __stdcall and returns a thread exit code.  _endthread
  likewise takes no parameter and calls ExitThread() with a
  parameter of zero, but _endthreadex() takes a parameter as
  thread exit code.

3)  _endthread implicitly closes the handle to the thread, but
  _endthreadex does not!

4)  _beginthread returns -1 for failure, _beginthreadex returns
  0 for failure (just like CreateThread).

อัปเดตม.ค. 2556:

CRT สำหรับ VS 2012 มีการเริ่มต้นบิตเพิ่มเติมที่ดำเนินการใน_beginthreadex(): หากกระบวนการเป็น "แอปแพ็กเกจ" (หากมีการส่งคืนสิ่งที่มีประโยชน์GetCurrentPackageId()) รันไทม์จะเริ่มต้น MTA บนเธรดที่สร้างขึ้นใหม่


3
มีเป็นเวลาที่เหมาะสมเมื่อ CreateThread () จะรับประกัน แต่อย่างสุจริตจริงๆคุณต้องออกไปจากทางของคุณที่จะทำมัน เรากำลังพูดถึงการขาดอุปกรณ์พกพาและการเขียน WIN32 API DLL หรือ App โดยเฉพาะ รวมถึงไม่มีการเรียก C-runtime แม้แต่การใช้งาน STL ก็มีข้อ จำกัด ที่คุณต้องจัดเตรียมตัวจัดสรรที่กำหนดเองเพื่อใช้ฟังก์ชันการจัดการหน่วยความจำ WIN32 การตั้งค่าเพื่อทำสิ่งนี้กับ Developer Studio เป็นงานในตัวมันเอง แต่สำหรับลิบ WIN32 ที่มีขนาดเล็กที่สุดเท่าที่จะทำได้ก็สามารถทำได้ แต่ใช่มันไม่น่าจะเป็นเลือดสำหรับเกือบทั้งหมด แต่มีให้เลือกน้อยมาก
WhozCraig

1
@WhozCraig: มีข้อ จำกัด ที่รุนแรงมากขึ้นเมื่อละเว้น CRT สิ่งที่โดดเด่นที่สุดคือ: ไม่มีการรองรับจำนวนเต็ม 64 บิต, ไม่มีการรองรับทศนิยมและ - อย่างมาก - ไม่มีการจัดการข้อยกเว้น นี่หมายความว่าไม่มีการจัดการข้อยกเว้น -เลย ไม่มีข้อยกเว้น SEH นี่เป็นเรื่องยากโดยเฉพาะอย่างยิ่งที่จะชดเชยและโอกาสในการเรียกตัวเองว่าCreateThreadเป็นสิ่งที่ถูกต้องมีน้อยลง
ระบุได้

@MichaelBurr: คุณอาจต้องการที่จะปรับปรุงคำตอบของคุณสำหรับ VC ++ 2015
541686

@ Mehrdad: การเปลี่ยนแปลงใดที่คุณคิดว่าควรค่าแก่การกล่าวถึงเป็นพิเศษ?
IInspectable

ฉันพบว่า DisableThreadLibraryCalls ไม่มีผลกับเธรดที่สร้างด้วย CreateThread แต่จะปิดใช้งานเธรดที่สร้างด้วย _beginthread หรือ _beginthreadex
SPlatten

24

โดยทั่วไปสิ่งที่ต้องทำคือเรียก_beginthread()/_endthread()(หรือex()ตัวแปร) อย่างไรก็ตามหากคุณใช้ CRT เป็น. dll สถานะ CRT จะเริ่มต้นอย่างเหมาะสมและถูกทำลายเนื่องจาก CRT DllMainจะถูกเรียกด้วยDLL_THREAD_ATTACHและDLL_THREAD_DETACHเมื่อเรียกCreateThread()และExitThread()หรือกลับมาตามลำดับ

DllMainสำหรับ CRT ที่สามารถพบได้ในไดเรกทอรีที่ติดตั้งสำหรับ VS ภายใต้ VC \ CRT \ src \ crtlib.c


จุดเริ่มต้นที่ดี ด้วยการดีบักเพียงเล็กน้อยเราสามารถแสดงว่า __CRTDLL_INIT ถูกเรียกแม้สำหรับ CRT ที่เชื่อมโยงแบบสแตติก Callstack ที่ init ถูกเรียกจาก _LdrpCallInitRoutine @ 16 () ฉันไม่แน่ใจว่าด้วยกลไกอะไร ซึ่งหมายความว่าเมื่อใช้ CRT ล่าสุดการเริ่มต้น / deinitialization ทั้งหมดจะทำได้อย่างถูกต้องยกเว้นการจัดการสัญญาณซึ่งยังคงทำในฟังก์ชันตัวช่วย _threadstartex ที่เรียกจาก beginthread แต่ไม่ใช่จาก CreateThread บางทีคุณอาจเพิ่มสิ่งนี้ลงในคำตอบและฉันจะให้รางวัล?
สุมา

ได้รับรางวัลเพราะสิ่งนี้มีประโยชน์มากที่สุด ถึงกระนั้นคำตอบอาจคุ้มค่าที่จะอัปเดต หากคุณไม่สามารถทำได้ฉันอาจกลับมาอีกครั้งภายในสองสามวัน
สุมา

1
@MSN: โปรดทราบว่า CreateThread ยังคงไม่ถูกต้องใน DLL หากคุณกำลังเชื่อมโยง CRT แบบคงที่อีกครั้งและเรียก DisableThreadLibraryCalls ซึ่งปิดใช้งานการเรียก DLL_THREAD_DETACH จากนั้นคุณจะได้รับการรั่วไหลของหน่วยความจำ นี่คือเอกสารที่นี่ในบทความ KB ของฉัน: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach

17

นี่คือรหัสที่อยู่หลักของ_beginthreadex(ดูcrt\src\threadex.c):

    /*
     * Create the new thread using the parameters supplied by the caller.
     */
    if ( (thdl = (uintptr_t)
          CreateThread( (LPSECURITY_ATTRIBUTES)security,
                        stacksize,
                        _threadstartex,
                        (LPVOID)ptd,
                        createflag,
                        (LPDWORD)thrdaddr))
         == (uintptr_t)0 )
    {
            err = GetLastError();
            goto error_return;
    }

ส่วนที่เหลือของการ_beginthreadexเริ่มต้นโครงสร้างข้อมูลต่อเธรดสำหรับ CRT

ข้อดีของการใช้_beginthread*คือการโทร CRT ของคุณจากเธรดจะทำงานได้อย่างถูกต้อง


13

คุณควรใช้ _beginthreadหรือ_beginthreadexเพื่ออนุญาตให้ไลบรารีรันไทม์ C ทำการเริ่มต้นเธรดของตัวเอง เฉพาะโปรแกรมเมอร์ C / C ++ เท่านั้นที่จำเป็นต้องรู้เรื่องนี้เนื่องจากตอนนี้ควรมีกฎการใช้สภาพแวดล้อมการพัฒนาของตนเอง

หากคุณใช้_beginthreadคุณไม่จำเป็นต้องโทรCloseHandleตามที่ RTL จะทำเพื่อคุณ _beginthreadนี่คือเหตุผลที่คุณไม่สามารถรอที่จับถ้าคุณได้ใช้ นอกจากนี้_beginthreadนำไปสู่ความสับสนหากฟังก์ชั่นเธรดออกทันที (อย่างรวดเร็ว) เนื่องจากเธรดการเปิดตัวฉันถูกปล่อยให้ถือเธรดที่ไม่ถูกต้องกับเธรดที่เพิ่งเปิดตัว

_beginthreadex สามารถใช้แฮนเดิลสำหรับการรอ แต่ยังต้องมีการโทรอย่างชัดเจน CloseHandleแต่ยังต้องมีการโทรที่ชัดเจนในการ นี่เป็นส่วนหนึ่งของสิ่งที่ทำให้ปลอดภัยสำหรับการใช้งานด้วยการรอ มีปัญหาอื่น ๆ ที่จะทำให้ไม่สามารถเข้าใจผิดได้อย่างสมบูรณ์คือการเริ่มต้นเธรดที่ถูกระงับเสมอ ตรวจสอบความสำเร็จจัดการบันทึก ฯลฯ เธรดประวัติย่อ สิ่งนี้จำเป็นเพื่อป้องกันไม่ให้เธรดยุติการทำงานก่อนที่เธรดการเรียกใช้งานจะสามารถบันทึกหมายเลขอ้างอิงได้

แนวทางปฏิบัติที่ดีที่สุดคือการใช้_beginthreadexเริ่มต้นระงับแล้วดำเนินการต่อหลังจากที่จับบันทึกรอที่จัดการได้จะCloseHandleต้องถูกเรียก


8

CreateThread()เคยมีการรั่วไหลของหน่วยความจำเมื่อคุณใช้ฟังก์ชัน CRT ใด ๆ ในรหัสของคุณ _beginthreadex()มีพารามิเตอร์เดียวกับและมันมากขึ้นหลากหลายกว่าCreateThread() ดังนั้นผมจึงขอแนะนำให้คุณใช้_beginthread()_beginthreadex()


2
บทความปี 1999 อาจได้รับการแก้ไขแล้ว
bobobobo

1
บทความนี้ตั้งแต่ปี 2548 ยังคงยืนยันว่ามีปัญหา
Jaywalker

2
ใช่มันใช้ได้กับ MSVC ++ 6.0 Service Pack 5 และรุ่นก่อนหน้าเท่านั้น (ดู "ใช้กับ" เมนูแบบเลื่อนลงที่ขยายได้) นี่ไม่ใช่ปัญหาในวันนี้หากคุณใช้ VC7 ขึ้นไป
bobobobo

1
นี่ยังคงเป็นปัญหาหากคุณเชื่อมโยง CRT แบบคงที่อีกครั้ง! นอกจากนี้ยังคงเป็นปัญหาหากคุณเรียก DisableThreadLibraryCalls ใน DLL ซึ่งเชื่อมโยงแบบคงที่ ดูบทความ KB ของฉัน: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach

2
คุณบิดเบือนข้อมูล: CreateThreadไม่ไม่เคยรั่วไหลของหน่วยความจำ มันค่อนข้างเป็น CRT ที่ทำเมื่อถูกเรียกจากเธรดที่ไม่ได้รับการเตรียมใช้งานอย่างถูกต้อง
ตรวจสอบได้

6

เกี่ยวกับคำถามที่อัปเดตของคุณ: "ฉันยังอ่านในสถานที่สองแห่งที่ฉันไม่สามารถโทรหาได้WaitForSingleObject()หากฉันใช้_beginthread()แต่ถ้าฉันเรียก_endthread()ในชุดข้อความนั้นจะไม่ได้ผลหรือ"

โดยทั่วไปคุณสามารถส่งที่จับเธรดไปยังWaitForSingleObject()(หรือ API อื่น ๆ ที่รออยู่บนจุดจับวัตถุ) เพื่อบล็อกจนกว่าเธรดจะเสร็จสมบูรณ์ แต่เธรดแฮนเดิลที่สร้างขึ้น_beginthread()จะถูกปิดเมื่อ_endthread()ถูกเรียก (ซึ่งสามารถทำได้อย่างชัดเจนหรือทำได้โดยปริยายตามเวลารันเมื่อโพรซีเดอร์เธรดกลับมา)

ปัญหาถูกระบุไว้ในเอกสารสำหรับWaitForSingleObject():

หากที่จับนี้ปิดในขณะที่รอดำเนินการอยู่จะไม่ได้กำหนดลักษณะการทำงานของฟังก์ชัน


5

มองไปที่ลายเซ็นฟังก์ชั่นที่เกือบจะเหมือนกับCreateThread_beginthreadex

_beginthread,_beginthreadxเทียบกับCreateThread

HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in       SIZE_T dwStackSize,
  __in       LPTHREAD_START_ROUTINE lpStartAddress,
  __in_opt   LPVOID lpParameter,
  __in       DWORD dwCreationFlags,
  __out_opt  LPDWORD lpThreadId
);

uintptr_t _beginthread( 
   void( *start_address )( void * ),
   unsigned stack_size,
   void *arglist 
);

uintptr_t _beginthreadex( 
   void *security,
   unsigned stack_size,
   unsigned ( *start_address )( void * ),
   void *arglist,
   unsigned initflag,
   unsigned *thrdaddr 
);

ข้อสังเกตในที่นี้กล่าวว่า_beginthreadสามารถใช้__cdeclหรือ__clrcallเรียกการประชุมเป็นจุดเริ่มต้นและ_beginthreadexสามารถใช้อย่างใดอย่างหนึ่ง__stdcallหรือ__clrcallสำหรับจุดเริ่มต้น

ฉันคิดว่าความคิดเห็นใด ๆ ที่ผู้คนแสดงความคิดเห็นเกี่ยวกับการรั่วไหลของหน่วยความจำCreateThreadนั้นมีอายุมากกว่าสิบปีและน่าจะถูกเพิกเฉย

ที่น่าสนใจคือทั้งสอง_beginthread*ฟังก์ชั่นเรียกใช้CreateThreadภายใต้ประทุนในC:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\srcเครื่องของฉัน

// From ~line 180 of beginthreadex.c
/*
 * Create the new thread using the parameters supplied by the caller.
 */
if ( (thdl = (uintptr_t)
      CreateThread( (LPSECURITY_ATTRIBUTES)security,
                    stacksize,
                    _threadstartex,
                    (LPVOID)ptd,
                    createflag,
                    (LPDWORD)thrdaddr))
         == (uintptr_t)0 )
{
        err = GetLastError();
        goto error_return;
}


3

beginthreadexช่วยให้คุณมีเธรดHANDLEสำหรับใช้ในWaitForSingleObjectและเพื่อน ๆ beginthreadไม่ อย่าลืมว่าCloseHandle()เมื่อคุณทำเสร็จแล้ว คำตอบที่แท้จริงคือการใช้boost::threadหรือเร็ว ๆ นี้คลาสเธรดของ C ++ 09


คำอธิบาย msdn ระบุว่า "หากสำเร็จแต่ละฟังก์ชันเหล่านี้จะส่งคืนหมายเลขอ้างอิงไปยังเธรดที่สร้างขึ้นใหม่" อ้างถึง _beginthread () และ _beginthreadex () ...
Kiril

@Kiril: แต่จากนั้นเอกสารก็บอกว่า _beginthread ปิดที่จับสำหรับคุณซึ่งหมายความว่าคุณจะไม่สามารถใช้มันได้หากเธรดออกอย่างรวดเร็ว ...
Roger Lipscombe

2

เมื่อเทียบ_beginthreadกับ_beginthreadexคุณสามารถ:

  1. ระบุแอตทริบิวต์ความปลอดภัย
  2. เริ่มเธรดในสถานะระงับ
  3. คุณจะได้รับด้าย id OpenThreadซึ่งสามารถนำมาใช้กับ
  4. ที่จับเธรดที่ส่งคืนจะรับประกันว่าใช้ได้หากการโทรสำเร็จ CloseHandleมีให้คุณจำเป็นต้องปิดการจัดการกับ
  5. หมายเลขอ้างอิงเธรดที่ส่งคืนสามารถใช้ได้กับ API การซิงโครไนซ์

_beginthreadexคล้ายCreateThreadแต่อดีตเป็นการนำ CRT และหลังการเรียกใช้ Windows API เอกสารสำหรับCreateThreadประกอบด้วยคำแนะนำต่อไปนี้:

เธรดในไฟล์ปฏิบัติการที่เรียกใช้ไลบรารีรันไทม์ C (CRT) ควรใช้ฟังก์ชัน_beginthreadexand _endthreadexสำหรับการจัดการเธรดมากกว่าCreateThreadและExitThread; ต้องใช้ CRT เวอร์ชันมัลติเธรด หากเธรดที่สร้างขึ้นโดยใช้การCreateThreadเรียก CRT CRT อาจยุติกระบวนการในสภาวะที่มีหน่วยความจำต่ำ


ตามข้อกำหนดของ API ที่จุด bullet 3-5 _beginthreadexไม่ได้ที่จะไม่ซ้ำกัน คุณสามารถโยนกลับมาจากฟังก์ชั่นทั้งสองuintptr_t HANDLE
Andon M. Coleman

ใช่คุณพูดถูกในทฤษฎี ในทางปฏิบัติความแตกต่างคือการ_beginthreadปิดที่จับเมื่อออก ดังนั้นคุณจึงไม่สามารถใช้แฮนเดิลกับ API การซิงโครไนซ์ได้อย่างน่าเชื่อถือหรือเพื่อรับรหัสเธรดจนกว่าคุณจะใช้วิธีอื่นในการซิงโครไนซ์และทำซ้ำหมายเลขอ้างอิง แต่มี_beginthreadexการทำเพื่อคุณ
Vishal

2

CreateThread()ครั้งหนึ่งเคยเป็น no-no เพราะ CRT จะเริ่มต้น / ล้างข้อมูลไม่ถูกต้อง แต่ตอนนี้เป็นประวัติศาสตร์: ตอนนี้เราสามารถโทรได้ (ใช้ VS2010 และอาจมีไม่กี่เวอร์ชัน) เรียกCreateThread()โดยไม่ทำลาย CRT

นี่คือการยืนยันอย่างเป็นทางการ MS มันระบุข้อยกเว้นประการหนึ่ง:

จริงๆแล้วฟังก์ชันเดียวที่ไม่ควรใช้ในเธรดที่สร้างด้วยCreateThread()คือsignal()ฟังก์ชัน

อย่างไรก็ตามจากมุมมองที่สอดคล้องกันฉันเองชอบที่จะใช้_beginthreadex()ต่อไป


แม้ว่าฉันคิดว่านี่เป็นความจริง แต่คุณสามารถให้หลักฐานที่เชื่อถือได้โดยการเชื่อมโยงไปยังเอกสาร MS หรือโดยการวิเคราะห์แหล่งที่มา CRT _beginthreadex / _endthreadex
สุมา

@Suma ฉันเดาว่าฉันเพิ่มลิงค์ MS ในขณะที่คุณพิมพ์ความคิดเห็นของคุณ ;-)
Serge Wautier

เอกสารที่คุณกำลังเชื่อมโยงดูเหมือนจะไม่ยืนยัน: "อย่างไรก็ตามขึ้นอยู่กับฟังก์ชัน CRT ที่เรียกว่าอาจมีหน่วยความจำรั่วเล็กน้อยเมื่อเธรดถูกยกเลิก" ซึ่งหมายความว่ามันไม่ใช่ no-no ใหญ่และทั่วไปอีกต่อไป แต่ก็ยังเป็น no-no หากคุณสร้างเธรดบ่อยๆและใช้ฟังก์ชันเหล่านั้นในเธรดเหล่านั้น อย่างไรก็ตามเอกสารดังกล่าวมาจากปี 2548 ดังนั้นจึงไม่สามารถระบุสถานะล่าสุดของเรื่องนี้ได้
สุมา

1
อืม ... แม้ว่ามันอาจจะขึ้นอยู่กับกรณีการใช้งาน แต่ฟังก์ชั่นที่ปล่อยให้หน่วยความจำรั่วไม่ว่าจะขนาดไหนฉันก็ถือว่าไม่ ... - โดยเฉพาะอย่างยิ่งถ้ามีทางเลือกอื่นที่ไม่รั่วไหล!
alk

"ตอนนี้สามารถเรียก CreateThread () โดยไม่ทำลาย CRT" - น่าเสียดายที่นี่ไม่เป็นความจริงและไม่เคยมีมาก่อน จากCreateThread : "เธรดในไฟล์ปฏิบัติการที่เรียกใช้ไลบรารีรันไทม์ C (CRT) ควรใช้ฟังก์ชัน _beginthreadex และ _endthreadex สำหรับการจัดการเธรด [... ] หากเธรดที่สร้างโดยใช้ CreateThread เรียก CRT CRT อาจยุติการ ประมวลผลในสภาวะความจำเหลือน้อย "
IInspectable

2

CreateThread()คือการเรียก Windows API ที่เป็นกลางของภาษา เพียงแค่สร้างวัตถุ OS - เธรดและส่งคืน HANDLE ไปยังเธรดนี้ แอปพลิเคชัน windows ทั้งหมดใช้การเรียกนี้เพื่อสร้างเธรด ทุกภาษาหลีกเลี่ยงการเรียก API โดยตรงด้วยเหตุผลที่ชัดเจน: 1. คุณไม่ต้องการให้รหัสของคุณเป็นระบบปฏิบัติการที่เฉพาะเจาะจง 2. คุณต้องทำการเก็บรักษาบางส่วนก่อนที่จะเรียก API เหมือน: แปลงพารามิเตอร์และผลลัพธ์จัดสรรพื้นที่เก็บข้อมูลชั่วคราวเป็นต้น

_beginthreadex()คือ C wrapper รอบ ๆCreateThread()บัญชีสำหรับ C เฉพาะ ช่วยให้ C f-n แบบเธรดเดี่ยวดั้งเดิมสามารถทำงานในสภาพแวดล้อมแบบมัลติเธรดได้โดยการจัดสรรหน่วยเก็บข้อมูลเฉพาะเธรด

หากคุณไม่ได้ใช้ CRT CreateThread()คุณไม่สามารถหลีกเลี่ยงการโทรโดยตรงไปยัง หากคุณใช้ CRT คุณต้องใช้มิ_beginthreadex()ฉะนั้นสตริง CRT f-n บางตัวอาจทำงานไม่ถูกต้องก่อน VC2005


1

CreateThread()เป็นการเรียกระบบตรง มีการใช้งานKernel32.dllซึ่งส่วนใหญ่แล้วแอปพลิเคชันของคุณจะถูกเชื่อมโยงด้วยเหตุผลอื่น ๆ พร้อมใช้งานในระบบ Windows สมัยใหม่เสมอ

_beginthread()และ_beginthreadex()เป็นฟังก์ชัน wrapper ใน Microsoft C Runtime ( msvcrt.dll) ความแตกต่างระหว่างการโทรทั้งสองมีระบุไว้ในเอกสารประกอบ ดังนั้นจึงพร้อมใช้งานเมื่อ Microsoft C Runtime พร้อมใช้งานหรือหากแอปพลิเคชันของคุณเชื่อมโยงแบบคงที่กับมัน คุณน่าจะเชื่อมโยงกับไลบรารีนั้นด้วยเว้นแต่คุณจะเขียนโค้ดใน Windows API ล้วนๆ (อย่างที่ฉันทำบ่อยๆ)

คำถามของคุณสอดคล้องกันและเป็นคำถามที่เกิดซ้ำ เนื่องจาก API จำนวนมากมีฟังก์ชันการทำงานที่ซ้ำกันและไม่ชัดเจนใน Windows API ที่เราต้องจัดการ ที่แย่ที่สุดคือเอกสารไม่ได้ชี้แจงปัญหา ฉันคิดว่า_beginthread()ตระกูลของฟังก์ชันถูกสร้างขึ้นเพื่อการทำงานร่วมกับฟังก์ชัน C มาตรฐานอื่น ๆ ที่ดีขึ้นเช่นการปรับแต่งerrno. _beginthread()จึงรวมเข้ากับรันไทม์ C ได้ดีขึ้น

แม้ว่าคุณจะมีเหตุผลที่ดีในการใช้_beginthread()หรือ_beginthreadex()คุณควรใช้CreateThread()ส่วนใหญ่เป็นเพราะคุณอาจได้รับการพึ่งพาไลบรารีน้อยลงหนึ่งไลบรารีในปฏิบัติการสุดท้ายของคุณ (และสำหรับ MS CRT สิ่งนี้มีความสำคัญเล็กน้อย) นอกจากนี้คุณยังไม่มีรหัสตัดรอบการโทรแม้ว่าเอฟเฟกต์นี้จะเล็กน้อยก็ตาม กล่าวอีกนัยหนึ่งฉันเชื่อว่าสาเหตุหลักของการติดด้วยCreateThread()คือไม่มีเหตุผลที่ดีที่จะใช้_beginthreadex()ในการเริ่มต้น ฟังก์ชันการทำงานมีความแม่นยำหรือเกือบจะเหมือนกัน

เหตุผลที่ดีอย่างหนึ่งที่ควรใช้_beginthread() คือ (ดูเหมือนจะเป็นเท็จ) ว่าวัตถุ C ++ จะถูกคลาย / ทำลายอย่างเหมาะสมหาก_endthread()ถูกเรียก


ไม่มีการเรียกฟังก์ชันคลุมเครืออยู่ที่ทุกคน CreateThreadคือการเรียก Windows API เพื่อสร้างเธรด หากคุณใช้ CRT (เนื่องจากคุณกำลังเขียนโปรแกรมด้วยภาษา C หรือ C ++) คุณควรสร้างเธรดโดยใช้การ_beginthread[ex]เรียกของ CRT (ซึ่งเรียกCreateThreadนอกเหนือจากการเริ่มต้น CRT ที่จำเป็น) ความแตกต่างที่สำคัญที่สุดระหว่าง_beginthreadและตัวแปรเดิม: อดีตยังคงเป็นเจ้าของที่จับเธรดเนทีฟในขณะที่ตัวหลังจะส่งต่อความเป็นเจ้าของไปยังผู้โทร
IInspectable

nitpick: msvcrt.dllคือไม่ DLL ที่รันไทม์ C! ดูblogs.msdn.microsoft.com/oldnewthing/20140411-00/?p=1273
Govind Parmar

0

คำตอบอื่น ๆ ไม่ได้กล่าวถึงผลของการเรียกใช้ฟังก์ชันรันไทม์ C ที่รวมฟังก์ชัน Win32 API นี่เป็นสิ่งสำคัญเมื่อพิจารณาพฤติกรรมการล็อกตัวโหลด DLL

หรือไม่ _beginthread{ex}จัดการหน่วยความจำ C Runtime thread / fiber แบบพิเศษตามที่คำตอบอื่น ๆ กล่าวถึงมันถูกนำไปใช้ใน (โดยสมมติว่ามีการเชื่อมโยงแบบไดนามิกไปยังรันไทม์ C) DLL ที่กระบวนการอาจยังไม่ได้โหลด

มันไม่ปลอดภัยที่จะเรียกจาก_beginthread* DllMainฉันได้ทดสอบสิ่งนี้โดยการเขียน DLL ที่โหลดโดยใช้คุณลักษณะ "AppInit_DLLs" ของ Windows การโทร_beginthreadex (...)แทนที่จะCreateThread (...)ทำให้ส่วนสำคัญของ Windows หยุดทำงานระหว่างการบูตเป็นไฟล์DllMainหยุดชะงักของจุดเริ่มต้นที่รอให้ปลดล็อกตัวโหลดเพื่อดำเนินการเริ่มต้นบางอย่าง

อนึ่งนี่เป็นสาเหตุที่kernel32.dllมีฟังก์ชันสตริงที่ทับซ้อนกันจำนวนมากซึ่งเวลาทำงานของ C ก็ใช้เช่นกัน - ใช้จากDllMainเพื่อหลีกเลี่ยงสถานการณ์แบบเดียวกัน


0

ถ้าคุณอ่านหนังสือแก้จุดบกพร่องของโปรแกรมประยุกต์ Windows จากเจฟฟรีย์ริกเตอร์ในนั้นเขาอธิบายว่าในเกือบทุกกรณีคุณต้องเรียกแทนการโทร_beginthreadex เป็นเพียงเสื้อคลุมรอบง่ายCreateThread_beginthread_beginthreadex

_beginthreadexเตรียมใช้งาน CRT (C RunTime) ภายในบางอย่างที่CreateThreadAPI จะไม่ทำ

ผลที่ตามมาหากคุณใช้CreateThreadAPI แทนการ_begingthreadexเรียกใช้ฟังก์ชัน CRT อาจทำให้เกิดปัญหาโดยไม่คาดคิด

ลองดูวารสาร Microsoft ฉบับเก่าจากริกเตอร์


0

คุณควรลองใช้รหัสนี้

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<process.h>

UINT __stdcall Staff(PVOID lp){
 printf("The Number is %d\n", GetCurrentThreadId());
 return 0;
}

INT main(INT argc, PCHAR argv[])
{

    const INT Staff_Number = 5;
    HANDLE hd[Staff_Number];
    for(INT i=0; i < Staff_Number; i++){
       hd[i] = (HANDLE)_beginthreadex(NULL, 0, Staff, NULL, 0, NULL);
    }

 WaitForMultipleObjects(Staff_Number, Staff, TRUE, NULL);
 for(INT i=0; i < Staff_Number; i++)
 {
     CloseHandle(hd[i]);
 }
 system("pause");
 return 0;
}

ถ้าคุณใช้ _beginthread แทน _beginthreadex มันจะทำให้เกิดข้อผิดพลาดมากเกินไปสำหรับ _beginthread นั่นเป็นเพราะ _beginthread ไม่สามารถสร้างเธรดที่มีแอตทริบิวต์ความปลอดภัยและฉันคิดว่า _beginthread ไม่จำเป็นคุณสามารถใช้ * (_ beginthreadex) และ CreateThread ได้


-2

ไม่มีความแตกต่างอีกต่อไประหว่างทั้งสอง

ความคิดเห็นทั้งหมดเกี่ยวกับการรั่วไหลของหน่วยความจำและอื่น ๆ อ้างอิงจาก <VS2005 เวอร์ชันเก่ามาก ฉันได้ทำการทดสอบความเครียดเมื่อหลายปีก่อนและสามารถหักล้างตำนานนี้ได้ แม้ Microsoft จะผสมผสานสไตล์ในตัวอย่างของพวกเขา แต่แทบจะไม่เคยใช้ _beginthread เลย


CreateThread : "ถ้าเธรดที่สร้างโดยใช้ CreateThread เรียก CRT CRT อาจยุติกระบวนการในสภาวะที่มีหน่วยความจำเหลือน้อย"
IInspectable

ขึ้นอยู่กับส่วนย่อย "ต้องใช้ CRT เวอร์ชันมัลติเธรด" ฉันถือว่านี่เป็นขยะเอกสารเนื่องจากไม่มีเวอร์ชัน crt แบบมัลติเธรดอีกต่อไปและเป็นเวลาหลายปีแล้ว
Lothar

"ไม่มีเวอร์ชัน CRT แบบมัลติเธรดอีกต่อไป" - MSDNอ้างว่า"[t] CRT แบบเธรดเดี่ยวไม่มีให้ใช้งานอีกต่อไป" คุณไม่สามารถถูกต้องทั้งคู่ ฉันจะไปกับ MSDN ที่นี่ด้วย
IInspectable

แน่นอนว่ามันเป็นการพิมพ์ผิดฉันหมายความว่าเธรดเดี่ยวหายไปและมัลติเธรดได้กลายเป็นมาตรฐานและสิ่งที่หายไปคือความแตกต่างระหว่างการใช้หรือไม่ใช้เธรด
Lothar

นี่เป็นเรื่องแปลกจริงๆ ตอนนี้คุณกำลังใช้คำสั่งที่ถูกต้องอย่างไม่ต้องสงสัย ( "ต้องใช้ CRT เวอร์ชันมัลติเธรด" ) เพื่ออ้างว่าทั้งคำสั่งนี้และส่วนที่เหลือของเอกสารมีโอกาสผิดพลาดมาก? แน่ใจว่าไม่ถูกต้อง
IInspectable
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.