スレッドを開始するより良い方法は、__beginthread
_、__beginthreadx
_、またはCreateThread
ですか?
__beginthread
_、__beginthreadex
_、およびCreateThread
の利点/欠点を判断しようとしています。これらの関数はすべて、新しく作成されたスレッドにスレッドハンドルを返します。エラーが発生するとCreateThreadが少しの追加情報を提供することを既に知っています(GetLastError
を呼び出すことで確認できます)...これらの機能をいつ使用するかを検討する必要がありますか?
私はWindowsアプリケーションで作業しているので、クロスプラットフォームの互換性はすでに問題外です。
私はmsdnのドキュメントを調べましたが、たとえば、なぜCreateThreadの代わりに_beginthreadを使用するか、またはその逆を選択するのかを理解できません。
乾杯!
更新:OK、すべての情報に感謝します。WaitForSingleObject()
を使用した場合は_beginthread()
を呼び出すことはできませんが、_endthread()
スレッドでそれは機能しませんか?そこの取り決めは何ですか?
CreateThread()
は、カーネルレベルで別の制御スレッドを作成するための未加工のWin32 API呼び出しです。
_beginthread()
&_beginthreadex()
は、CreateThread()
をバックグラウンドで呼び出すCランタイムライブラリの呼び出しです。 CreateThread()
が返されると、_beginthread/ex()
が追加のブックキーピングを処理し、新しいスレッドでCランタイムライブラリを使用可能かつ一貫性のあるものにします。
C++では、Cランタイムライブラリ(別名MSVCRT * .dll/.lib)にまったくリンクしない場合を除き、ほぼ確実に_beginthreadex()
を使用する必要があります。
_beginthread()
と_beginthreadex()
にはいくつかの違いがあります。 _beginthreadex()
はCreateThread()
のように動作するように作成されました(両方のパラメーターとその動作において)。
Drew Hall が述べているように、C/C++ランタイムを使用している場合、_beginthread()
の代わりに_beginthreadex()
/CreateThread()
を使用する必要があります。ランタイムは、独自のスレッド初期化(スレッドローカルストレージのセットアップなど)を実行する機会があります。
実際には、これはCreateThread()
がコードによって直接使用されることはほとんどないことを意味します。
_beginthread()
/_beginthreadex()
のMSDNドキュメントには、違いについてかなり詳細な情報があります。より重要なことの1つは、_beginthread()
によって作成されたスレッドのスレッドハンドルが「_beginthreadによって生成されたスレッドがすぐに終了する場合、_beginthreadの呼び出し元に返されたハンドルは無効であるか、さらに悪いことに別のスレッドを指している」.
CRTソースの_beginthreadex()
に対するコメントは次のとおりです。
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).
更新2013年1月:
VS 2012のCRTには、_beginthreadex()
で追加の初期化が行われます。プロセスが「パッケージアプリ」の場合(GetCurrentPackageId()
から何か有用なものが返される場合)、ランタイムはMTAを初期化します新しく作成されたスレッド。
これは、_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呼び出しが正しく機能することです。
_beginthread
または_beginthreadex
を使用して、Cランタイムライブラリが独自のスレッドの初期化を行えるようにする必要があります。 C/C++プログラマーのみがこれを知る必要があります。これは、独自の開発環境を使用するルールを理解する必要があるためです。
_beginthread
を使用する場合、RTLが行うようにCloseHandle
を呼び出す必要はありません。 _beginthread
を使用した場合、ハンドルを待つことができないのはこのためです。また、_beginthread
は、スレッド関数がすぐに(迅速に)終了すると混乱を引き起こします。これは、起動スレッドが起動したばかりのスレッドへの無効なスレッドハンドルを保持しているためです。
_beginthreadex
ハンドルは待機に使用できますが、CloseHandle
の明示的な呼び出しも必要です。これは、waitを使用しても安全な理由の一部です。それを完全に確実にする他の問題は、常に中断されたスレッドを開始することです。成功、レコードハンドルなどを確認します。再開スレッド。これは、起動スレッドがハンドルを記録できるようになる前にスレッドが終了するのを防ぐために必要です。
ベストプラクティスは、_beginthreadex
を使用し、サスペンドを開始し、ハンドルの記録後に再開し、ハンドルでの待機はOKで、CloseHandle
を呼び出す必要があります。
CreateThread()
はメモリリークが発生していました コードでCRT関数を使用する場合。 _beginthreadex()
にはCreateThread()
と同じパラメーターがあり、_beginthread()
よりも汎用性があります。したがって、_beginthreadex()
を使用することをお勧めします。
関数シグネチャを見ると、CreateThread
は_beginthreadex
とほとんど同じです。
_beginthread
、_beginthreadx
vs 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
);
here の備考では、_beginthread
が__cdecl
または__clrcall
呼び出し規約を開始点として使用でき、_beginthreadex
が__stdcall
または__clrcall
を開始点として使用できます。
CreateThread
のメモリリークに関する人々のコメントは10年以上前のものであり、おそらく無視すべきだと思います。
興味深いことに、両方の_beginthread*
関数は、実際には、マシンのC:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src
で、内部でCreateThread
を呼び出します。
// 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;
}
更新された質問について:「WaitForSingleObject()
を使用した場合、_beginthread()
を呼び出せないが、_endthread()
を呼び出した場合、スレッドは動作しませんか?」
一般に、スレッドハンドルをWaitForSingleObject()
(またはオブジェクトハンドルで待機する他のAPI)に渡して、スレッドが完了するまでブロックできます。ただし、_beginthread()
が呼び出されると、_endthread()
によって作成されたスレッドハンドルは閉じられます(これは、明示的に実行するか、スレッドプロシージャが戻るときに実行時に暗黙的に実行されます)。
問題はWaitForSingleObject()
のドキュメントに記載されています。
待機がまだ保留中にこのハンドルが閉じられた場合、関数の動作は未定義です。
beginthreadex
は、HANDLE
および友人で使用するスレッドWaitForSingleObject
を提供します。 beginthread
はしません。完了したら、CloseHandle()
を忘れないでください。本当の答えは、boost::thread
を使用するか、まもなくC++ 09のスレッドクラスを使用することです。
に比べ _beginthread
、_beginthreadex
あなたはできる:
OpenThread
で使用できるスレッドIDを取得できます。CloseHandle
でハンドルを閉じる必要があります。_beginthreadex
はCreateThread
によく似ていますが、前者はCRT実装で、後者はWindows API呼び出しです。 CreateThread のドキュメントには、次の推奨事項が含まれています。
Cランタイムライブラリ(CRT)を呼び出す実行可能ファイル内のスレッドは、
_beginthreadex
および_endthreadex
CreateThread
およびExitThread
;これには、マルチスレッドバージョンのCRTを使用する必要があります。CreateThread
を使用して作成されたスレッドがCRTを呼び出すと、CRTはメモリ不足の状態でプロセスを終了する場合があります。
CreateThread()
は、言語に依存しないWindows API呼び出しです。 OSオブジェクト-スレッドを作成し、このスレッドにハンドルを返します。すべてのWindowsアプリケーションは、この呼び出しを使用してスレッドを作成しています。すべての言語は、明らかな理由で直接API呼び出しを回避します。
_beginthreadex()
はCreateThread()
のCラッパーで、C固有のものを考慮します。スレッド固有のストレージを割り当てることにより、マルチスレッド環境で元のシングルスレッドC f-nsを動作させることができます。
CRTを使用しない場合、CreateThread()
の直接呼び出しを避けることはできません。 CRTを使用する場合は、_beginthreadex()
を使用する必要があります。そうしないと、VC2005以前では一部のCRT文字列f-nsが正常に機能しない場合があります。
CreateThread()
onceは、CRTが誤って初期化/クリーンアップされるため、no-noでした。しかし、これが今の歴史です。CRTを壊さずにCreateThread()
を呼び出すことができます(VS2010とおそらくいくつかのバージョンを使用して)。
ここに公式のMS確認があります 。例外が1つあります。
実際、
CreateThread()
で作成されたスレッドで使用すべきでない唯一の関数はsignal()
関数です。
しかし、一貫性の観点から、私は個人的に_beginthreadex()
を使い続けることを好みます。
CreateThread()
はストレートシステムコールです。これはKernel32.dll
に実装されており、ほとんどの場合、アプリケーションは他の理由で既にリンクされています。最新のWindowsシステムでは常に利用可能です。
_beginthread()
および_beginthreadex()
は、Microsoft Cランタイムのラッパー関数(msvcrt.dll
)です。 2つの呼び出しの違いは、ドキュメントに記載されています。したがって、Microsoft Cランタイムが使用可能な場合、またはアプリケーションが静的にリンクされている場合に使用できます。純粋なWindows APIでコーディングしている場合を除き(おそらく私が個人的によく行っているように)、そのライブラリに対してもリンクする可能性があります。
あなたの質問は首尾一貫したものであり、実際には反復的なものです。多くのAPIと同様に、Windows APIには対処する必要がある重複したあいまいな機能があります。最悪なことに、ドキュメントでは問題が明確になっていません。 _beginthread()
ファミリの関数は、errno
の操作など、他の標準C機能との統合を改善するために作成されたと思われます。 _beginthread()
は、Cランタイムとより良く統合します。
それにもかかわらず、_beginthread()
または_beginthreadex()
を使用する正当な理由がない限り、CreateThread()
を使用する必要があります。これは、主に最終実行可能ファイルのライブラリ依存性が1つ少ないためです(MS CRTでは少し重要です)。また、呼び出しをラップするコードはありませんが、この効果は無視できます。言い換えれば、CreateThread()
にこだわる主な理由は、最初に_beginthreadex()
を使用する正当な理由がないからだと思います。機能は正確に、またはほぼ同じです。
_beginthread()
を使用する正当な理由の1つは、_endthread()
が呼び出された場合にC++オブジェクトが適切に巻き戻されたり破棄されたりするになることです。
本の中でJeffrey RichterのDebugging Windows Applicationを読むと、ほとんどすべての場合、CreateThread
を呼び出す代わりに_beginthreadex
を呼び出す必要があると説明しています。 _beginthread
は、_beginthreadex
の単純なラッパーです。
_beginthreadex
は、CreateThread
APIではできない特定のCRT(C RunTime)内部を初期化します。
CRT関数への_begingthreadex
呼び出しの代わりにCreateThread
APIを使用すると、予期しない問題が発生する可能性があります。
他の回答では、Win32 API関数をラップするCランタイム関数を呼び出すことの意味を説明できません。これは、DLLローダーのロック動作を考慮する場合に重要です。
__beginthread{ex}
_が他の回答で説明しているように特別なCランタイムスレッド/ファイバーメモリ管理を行うかどうかにかかわらず、それは(Cランタイムへの動的リンクを想定して)DLLに実装されますまだロードしていません。
DllMain
から__beginthread*
_を呼び出すのは安全ではありません。 Windowsの「AppInit_DLLs」機能を使用してロードされたDLLを記述して、これをテストしました。 _beginthreadex (...)
の代わりにCreateThread (...)
を呼び出すと、ローダーのロックが解除されるのを待っているDllMain
エントリポイントデッドロックが発生するため、Windowsの重要な部分の多くが起動中に機能しなくなります特定の初期化タスクを実行するため。
ちなみに、これがkernel32.dllがCランタイムにもある多くの重複する文字列関数を持っている理由でもあります-同じような状況を避けるためにDllMain
の関数を使用します。