Jaki jest lepszy sposób na rozpoczęcie wątku _beginthread
, _beginthreadx
czy CreateThread
?
Próbuję ustalić, jakie są zalety / wady _beginthread
, _beginthreadex
i CreateThread
. Wszystkie te funkcje zwracają uchwyt wątku do nowo utworzonego wątku, już wiem, że CreateThread dostarcza trochę dodatkowych informacji, gdy wystąpi błąd (można to sprawdzić, wywołując GetLastError
) ... ale jakie są rzeczy, które powinienem rozważyć, kiedy ja ' korzystam z tych funkcji?
Pracuję z aplikacją Windows, więc kompatybilność między platformami jest już wykluczona.
Przejrzałem dokumentację msdn i po prostu nie rozumiem, na przykład, dlaczego ktoś miałby zdecydować się na użycie _beginthread zamiast CreateThread lub odwrotnie.
Twoje zdrowie!
Aktualizacja: OK, dzięki za wszystkie informacje, przeczytałem też w kilku miejscach, do których nie mogę zadzwonić, WaitForSingleObject()
gdybym korzystał _beginthread()
, ale jeśli dzwonię _endthread()
w wątku, czy to nie powinno działać? O co tam chodzi?
źródło
Odpowiedzi:
CreateThread()
to surowe wywołanie API Win32 do tworzenia kolejnego wątku kontroli na poziomie jądra._beginthread()
&_beginthreadex()
to wywołania biblioteki wykonawczej C, które wywołująCreateThread()
za kulisami. PoCreateThread()
powrocie_beginthread/ex()
zajmuje się dodatkową księgowością, aby biblioteka środowiska wykonawczego C była użyteczna i spójna w nowym wątku.W C ++ prawie na pewno powinieneś używać,
_beginthreadex()
chyba że w ogóle nie będziesz łączyć się z biblioteką wykonawczą C (aka MSVCRT * .dll / .lib).źródło
_begin
wywołać procedury wewnętrzne, aCreateThread
miało być funkcją API, którą wszyscy będą wywoływać. Innym potencjalnym wyjaśnieniem jest to, że stwardnienie rozsiane ma długą i wspaniałą historię ignorowania standardów i podejmowania bardzo złych decyzji dotyczących nazewnictwa rzeczy._begin
funkcje zaczynają się od podkreślenia , ponieważ Microsoft zaczął zgodne ze standardem bliżej. W środowisku wykonawczym C nazwy z podkreśleniem są zarezerwowane dla implementacji (a implementacja może je udokumentować do użytku przez użytkownika końcowego, tak jak w przypadku tych).beginthreadex()
to nazwa, której może używać użytkownik. Gdyby środowisko wykonawcze C go użyło, mogłoby to powodować konflikt z symbolem użytkownika końcowego, którego użytkownik miał uzasadnione prawo, aby móc go używać. Należy zauważyć, że interfejsy API Win32 nie są częścią środowiska wykonawczego C i używają przestrzeni nazw użytkownika.CreateThread
i połączeniami CRT_beginthread/ex
, a podczas wywoływania CRT na wątku, to zawsze powinny być tworzone z_beginthread/ex
. Jeśli tego nie zrobisz, może już nie być wycieków pamięci. Ale z pewnością nie otrzymasz poprawnie zainicjalizowanego środowiska zmiennoprzecinkowegoCreateThread
, na przykład podczas wywoływania . Jest więcej : „Jeśli wątek utworzony za pomocą CreateThread wywołuje CRT, CRT może zakończyć proces w warunkach małej ilości pamięci”.Istnieje kilka różnic między
_beginthread()
i_beginthreadex()
._beginthreadex()
został stworzony, aby zachowywać się bardziej podobnieCreateThread()
(zarówno pod względem parametrów, jak i zachowania).Jak wspomina Drew Hall , jeśli używasz środowiska uruchomieniowego C / C ++, musisz użyć
_beginthread()
/_beginthreadex()
zamiast,CreateThread()
aby środowisko wykonawcze miało szansę na wykonanie własnej inicjalizacji wątku (ustawienie lokalnego magazynu wątku itp.).W praktyce oznacza to, że
CreateThread()
prawie nigdy nie powinno być używane bezpośrednio w kodzie.Dokumenty MSDN dla
_beginthread()
/_beginthreadex()
zawierają sporo szczegółów na temat różnic - jednym z ważniejszych jest to, że ponieważ uchwyt wątku dla wątku utworzonego przez_beginthread()
jest automatycznie zamykany przez CRT po zakończeniu wątku, „jeśli wątek wygenerowany przez _beginthread kończy działanie szybko uchwyt zwrócony do obiektu wywołującego _beginthread może być nieprawidłowy lub, co gorsza, wskazywać na inny wątek ".Oto, co
_beginthreadex()
mają do powiedzenia komentarze w źródle CRT:Aktualizacja styczeń 2013:
CRT dla VS 2012 ma dodatkowy bit inicjalizacji wykonywany w
_beginthreadex()
: jeśli proces jest „spakowaną aplikacją” (jeśli zwracane jest coś użytecznegoGetCurrentPackageId()
), środowisko wykonawcze zainicjuje MTA w nowo utworzonym wątku.źródło
CreateThread
że zadzwonisz do właściwej osoby, są coraz mniejsze.Ogólnie rzecz biorąc, właściwą rzeczą do zrobienia jest wywołanie
_beginthread()/_endthread()
(lubex()
warianty). Jeśli jednak użyjesz CRT jako pliku dll, stan CRT zostanie poprawnie zainicjowany i zniszczony, gdy CRTDllMain
zostanie wywołany odpowiednio zDLL_THREAD_ATTACH
iDLL_THREAD_DETACH
podczas wywoływaniaCreateThread()
iExitThread()
lub powrotu.DllMain
Kod na CRT można znaleźć w katalogu instalacyjnym VS pod VC \ crt \ src \ crtlib.c.źródło
To jest kod leżący u podstaw
_beginthreadex
(zobaczcrt\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; }
Reszta
_beginthreadex
inicjuje strukturę danych na wątek dla CRT.Zaletą używania
_beginthread*
jest to, że wywołania CRT z wątku będą działać poprawnie.źródło
Powinieneś użyć
_beginthread
lub,_beginthreadex
aby pozwolić bibliotece wykonawczej C na wykonanie własnej inicjalizacji wątku. Tylko programiści C / C ++ muszą to wiedzieć, ponieważ powinni teraz znać zasady korzystania z własnego środowiska programistycznego.Jeśli używasz
_beginthread
, nie musisz dzwonić,CloseHandle
ponieważ RTL zrobi to za Ciebie. Dlatego nie możesz czekać na uchwycie, jeśli użyłeś_beginthread
._beginthread
Prowadzi również do zamieszania, jeśli funkcja wątku kończy pracę natychmiast (szybko), ponieważ wątek uruchamiający może pozostać z nieprawidłowym uchwytem wątku do wątku, który właśnie uruchomił._beginthreadex
uchwyty mogą służyć do oczekiwania, ale wymagają również jawnego wywołaniaCloseHandle
. Jest to część tego, co sprawia, że można ich bezpiecznie używać z czekaniem. Innym problemem, który sprawia, że jest całkowicie niezawodny, jest zawsze rozpoczynanie zawieszonego wątku. Sprawdź powodzenie, uchwyt rekordu itp. Wątek wznowienia. Jest to wymagane, aby zapobiec zakończeniu wątku, zanim wątek uruchamiający będzie mógł zarejestrować swój uchwyt.Najlepszą praktyką jest użycie
_beginthreadex
, rozpoczęcie zawieszenia, a następnie wznowienie po nagraniu uchwytu, oczekiwanie na uchwyt jest OK,CloseHandle
musi zostać wywołane.źródło
CreateThread()
kiedyś występowały przecieki pamięci podczas używania funkcji CRT w kodzie._beginthreadex()
ma takie same parametry jakCreateThread()
i jest bardziej wszechstronny niż_beginthread()
. Więc polecam ci użycie_beginthreadex()
.źródło
CreateThread
czy nie kiedykolwiek wyciek pamięci. To raczej CRT robi to, gdy jest wywoływany z wątku, który nie został poprawnie zainicjowany.Odnosząc się do twojego zaktualizowanego pytania: „Czytałem też w kilku miejscach, do których nie mogę zadzwonić,
WaitForSingleObject()
jeśli użyłem_beginthread()
, ale jeśli dzwonię_endthread()
w wątku, czy to nie powinno działać?”Ogólnie rzecz biorąc, można przekazać uchwyt wątku do
WaitForSingleObject()
(lub inne interfejsy API, które czekają na uchwyty obiektów), aby blokować do momentu zakończenia wątku. Ale uchwyt wątku utworzony przez_beginthread()
jest zamykany, gdy_endthread()
jest wywoływany (co można zrobić jawnie lub niejawnie przez czas wykonywania, gdy procedura wątku zwraca).Problem pojawia się w dokumentacji
WaitForSingleObject()
:źródło
Patrząc na sygnatury funkcji,
CreateThread
jest prawie identyczny z_beginthreadex
._beginthread
,_beginthreadx
vsCreateThread
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 );
Uwagi na temat tutaj mówią , że
_beginthread
można użyć konwencji wywoływania albo__cdecl
lub__clrcall
jako punktu początkowego, a jako punktu początkowego_beginthreadex
można użyć albo__stdcall
albo__clrcall
.Myślę, że wszelkie komentarze ludzi na temat wycieków pamięci
CreateThread
mają ponad dekadę i prawdopodobnie powinny zostać zignorowane.Co ciekawe, obie
_beginthread*
funkcje faktycznie wywołująCreateThread
pod maską, wC:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src
moim komputerze.// 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; }
źródło
beginthreadex
daje ci wątekHANDLE
do wykorzystania wWaitForSingleObject
i znajomych.beginthread
nie. Nie zapomnij,CloseHandle()
kiedy skończysz. Prawdziwą odpowiedzią byłoby użycieboost::thread
lub wkrótce C ++ 09 klasy wątku.źródło
W porównaniu
_beginthread
z_beginthreadex
, możesz:OpenThread
.CloseHandle
.To
_beginthreadex
bardzo przypominaCreateThread
, ale pierwsza to implementacja CRT, a druga wywołanie Windows API. Dokumentacja narzędzia CreateThread zawiera następujące zalecenia:źródło
_beginthreadex
. Możesz rzutowaćuintptr_t
powrót z obu funkcji naHANDLE
._beginthread
że przy wyjściu zamyka klamkę. Nie możesz więc niezawodnie używać uchwytu z synchronizacyjnymi interfejsami API lub uzyskać identyfikator wątku, dopóki nie użyjesz innego sposobu synchronizacji i zduplikowania uchwytu. Ale potem_beginthreadex
robi to za Ciebie.CreateThread()
kiedyś było nie-nie, ponieważ CRT byłby nieprawidłowo inicjowany / czyszczony. Ale to już historia: można teraz (używając VS2010 i prawdopodobnie kilku wersji wstecz) dzwonićCreateThread()
bez zrywania CRT.Oto oficjalne potwierdzenie MS . Stanowi jeden wyjątek:
Jednak z punktu widzenia spójności osobiście wolę nadal używać
_beginthreadex()
.źródło
CreateThread()
to wywołanie interfejsu API systemu Windows, które jest neutralne językowo. Po prostu tworzy obiekt systemu operacyjnego - wątek i zwraca HANDLE do tego wątku. Wszystkie aplikacje systemu Windows używają tego wywołania do tworzenia wątków. Wszystkie języki unikają bezpośredniego wywołania API z oczywistych powodów: 1. Nie chcesz, aby Twój kod był specyficzny dla systemu operacyjnego 2. Przed wywołaniem interfejsu API musisz wykonać pewne czynności konserwacyjne: przekonwertować parametry i wyniki, przydzielić tymczasową pamięć itp._beginthreadex()
jest opakowaniem C,CreateThread()
które jest specyficzne dla języka C. Umożliwia pracę oryginalnych jednowątkowych C f-ns w środowisku wielowątkowym poprzez przydzielanie pamięci specyficznej dla wątku.Jeśli nie używasz CRT, nie możesz uniknąć bezpośredniego połączenia
CreateThread()
. Jeśli używasz CRT, musisz użyć_beginthreadex()
lub niektóre f-ns z ciągiem CRT mogą nie działać poprawnie przed VC2005.źródło
CreateThread()
jest prostym wywołaniem systemowym. Jest zaimplementowany, naKernel32.dll
którym najprawdopodobniej Twoja aplikacja będzie już połączona z innymi powodami. Jest zawsze dostępny w nowoczesnych systemach Windows._beginthread()
i_beginthreadex()
są funkcjami opakowania w Microsoft C Runtime (msvcrt.dll
). Różnice między tymi dwoma zaproszeniami są opisane w dokumentacji. Jest zatem dostępny, gdy środowisko wykonawcze języka Microsoft C jest dostępne lub jeśli aplikacja jest z nim połączona statycznie. Prawdopodobnie będziesz również łączyć się z tą biblioteką, chyba że kodujesz w czystym interfejsie API systemu Windows (co osobiście często robię).Twoje pytanie jest spójne i faktycznie powracające. Podobnie jak wiele interfejsów API, mamy do czynienia z zduplikowanymi i niejednoznacznymi funkcjami w interfejsie Windows API. Co najgorsze, dokumentacja nie wyjaśnia sprawy. Przypuszczam, że
_beginthread()
rodzina funkcji została stworzona w celu lepszej integracji z innymi standardowymi funkcjami C, takimi jak manipulacjaerrno
._beginthread()
dzięki temu lepiej integruje się ze środowiskiem wykonawczym C.Mimo to, chyba że masz dobre powody, aby używać
_beginthread()
lub_beginthreadex()
, powinieneś używaćCreateThread()
, głównie dlatego, że możesz uzyskać jedną zależność od biblioteki mniej w swoim ostatecznym pliku wykonywalnym (a dla MS CRT ma to trochę znaczenia). Nie masz również zawijającego się kodu wokół wywołania, chociaż ten efekt jest pomijalny. Innymi słowy, uważam, że głównym powodem, dlaCreateThread()
którego warto się trzymać, jest to, że nie ma dobrego powodu,_beginthreadex()
aby zacząć od tego. Funkcjonalności są dokładnie lub prawie takie same.Jednym z dobrych powodów użycia
_beginthread()
byłoby (jak się wydaje fałszywe), że obiekty C ++ byłyby prawidłowo rozwijane / niszczone, gdyby_endthread()
zostały wywołane.źródło
CreateThread
to wywołanie interfejsu API systemu Windows w celu utworzenia wątku. Jeśli używasz CRT (ponieważ programujesz w C lub C ++), powinieneś utworzyć wątki przy użyciu_beginthread[ex]
wywołań CRT (któreCreateThread
oprócz wykonania niezbędnej inicjalizacji CRT). Najważniejsza różnica między_beginthread
i byłym wariantem: pierwszy zachowuje własność natywnego uchwytu wątku, podczas gdy drugi przekazuje własność wywołującemu.msvcrt.dll
jest biblioteką DLL środowiska wykonawczego C! Zobacz blogs.msdn.microsoft.com/oldnewthing/20140411-00/?p=1273Inne odpowiedzi nie omawiają konsekwencji wywołania funkcji czasu wykonywania C, która otacza funkcję API Win32. Jest to ważne przy rozważaniu sposobu blokowania modułu ładującego DLL.
Niezależnie od tego, czy
_beginthread{ex}
jakikolwiek specjalny sposób zarządzania pamięcią wątków / światłowodów środowiska wykonawczego języka C, o którym mowa w innych odpowiedziach, jest zaimplementowany w (zakładając dynamiczne łączenie z programem C Runtime) w bibliotece DLL, której procesy mogły jeszcze nie zostać załadowane.To nie jest bezpieczne, aby zadzwonić
_beginthread*
zDllMain
. Przetestowałem to, pisząc bibliotekę DLL załadowaną przy użyciu funkcji „AppInit_DLLs” systemu Windows. Wywołanie_beginthreadex (...)
zamiastCreateThread (...)
powoduje, że WIELE ważnych części systemu Windows przestaje działać podczas rozruchu jakoDllMain
zakleszczenie punktu wejścia, czekające na zwolnienie blokady modułu ładującego w celu wykonania pewnych zadań inicjalizacyjnych.Nawiasem mówiąc, jest to również powód, dla którego kernel32.dll ma wiele nakładających się funkcji tekstowych, które ma również środowisko wykonawcze C - użyj tych z,
DllMain
aby uniknąć tego samego rodzaju sytuacji.źródło
Jeśli przeczytasz w niej książkę Debugowanie aplikacji systemu Windows od Jeffreya Richtera, wyjaśnia on, że prawie we wszystkich przypadkach musisz dzwonić
_beginthreadex
zamiast dzwonićCreateThread
._beginthread
to tylko uproszczone opakowanie_beginthreadex
._beginthreadex
inicjuje pewne wewnętrzne funkcje CRT (C RunTime), którychCreateThread
interfejs API nie zrobiłby.Konsekwencją korzystania z
CreateThread
interfejsu API zamiast używania_begingthreadex
wywołań funkcji CRT może być nieoczekiwana przyczyna problemów.Sprawdź ten stary Microsoft Journal od Richtera.
źródło
Powinieneś wypróbować ten kod
#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; }
jeśli użyjesz _beginthread zamiast _beginthreadex, spowoduje to błąd zbyt wielu argumentów dla _beginthread, ponieważ _beginthread nie mógł utworzyć wątku z atrybutem bezpieczeństwa, a także myślę, że _beginthread jest niepotrzebny, możesz bezwzględnie użyć * (_ beginthreadex) i CreateThread
źródło
Nie ma już różnicy między nimi.
Wszystkie komentarze dotyczące wycieków pamięci itp. Dotyczą bardzo starych wersji <VS2005. Wiele lat temu przeprowadziłem testy warunków skrajnych i mogę obalić ten mit. Nawet Microsoft miesza style w swoich przykładach, prawie nigdy nie używając _beginthread.
źródło