《Win32多线程程序设计》–Jim Beveridge & Robert Wiener
C runtime library
单线程版本:
当 C runtime library 于 20 世纪 70 年代产生出来时,内存容量还很小,多任务是个新奇观念,更别提什么多线程了。C runtime library 使用数个全局变量和静态变量,这可能在多线程程序中彼此引起冲突。- 使用全局变量和静态变量
最为大众所知的就是 errno ,当 runtime library 发生错误时(特别是文件相关函数),其值会被设定。请你想一想,如果两个线程都以 FILE* 函数进行文件 I/O 操作,而两者都设定 errno,会发生什么事呢?很明显这是一个 race condition,其中一个线程会得到错误的结果。另一个例子是字符串函数 strtok() ,它会维护一个指针,指向目前正被处理的字符串。 - 一些数据结构
C runtime library 中还有一些数据结构也必须被修改为“ thread-safe”(在多线程情况下保证安全)。例如 fopen() 传回的一个 FILE* ,基本上是一个指针指向 runtime library 中的一个描述表格(descriptor table)。这个描述表格必须以同步机制保护之,以避免冲突。 - 内存处理函数
runtime library 也有可能进行它自己的内存配置操作,这时候内存处理函数也必须被保护。
- 使用全局变量和静态变量
多线程版本:
使用Win32同步机制,就有可能产生一个 runtime library,支持多线程。问题是加上那样的支持之后,在大小以及效率两方面都可能因为同步机制的执行而遭受不良波及——甚至即使你只启动了一个线程。
Visual C++ 的折衷方案是提供两个版本的 C runtime library。一个版本给单线程程序使用, 一个版本给多线程程序使用。多线程版有两个很大的差别:- 如 errno 之流的变量,现在变成每个线程各拥有一个。
- 多线程版中的数据结构以同步机制加以保护。
MFC 程序必须使用多线程版的 C runtime library,否则你就会在链接时获得“undefined function”的错误信息。
_beginthread()的问题:
_beginthread() 被认为是一个头脑简单的函数。它存在几个基本问题:
- _beginthread() 并没有要求获得和 CreateThread() 完全一样的参数,因此有些事情它就办不到,如将线程产生于挂起状态,以便优先权可调整以及数据可被初始化等等。
- 被 _beginthread() 产生出来的线程所做的第一件事就是关闭自己的 handle。这样做是为了隐藏 Win32 的实现细节。因此,_beginthread() 传回的 handle 可能在当时是不可用(invalid)的。如果你尝试使用由 _beginthread() 传回的 handle,无可避免会导致一个 race condition。没有这个 handle ,也就没有办法等待这个线程的结束,改变其参数、或甚至取得其结束代码。
- 对着一个以 _beginthread() 产生出来的线程调用 GetExitCodeThread() 是没有意义的。让任何一个以 _beginthread() 产生出来的线程传回一个结束代码同样地毫无意义。
CreateThread()和_beginthreadex()
为了保证多线程情况下的安全, runtime library 必须为每一个由它所启动和结束的线程做一些簿记工夫。没有这些簿记工作, runtime library 就不知道要为每一个线程配置一块新的内存,作为线程的局部变量用。因此,CreateThread() 有一个名为_beginthreadex() 的外包函数,负责额外的簿记工作。
虽然从 _beginthreadex() 的声明中不能明显看出,然而事实上它所传回的 unsigned long 是一个 Win32 HANDLE,指向新的线程。亦或者传回的是 0——如果函数失败的话。换句话说,传回值和 CreateThread() 相同,但 _beginthreadex() 另外还设立了 errno 和 doserrno 。
由于_beginthreadex() 调用 CreateThread(),所以你必须对着_beginthreadex()的传回值调用 CloseHandle() 。
1. 绝对不要在一个“以 _beginthreadex() 启动的线程”中调用ExitThread(),因为这么一来,C runtime library 就没有机会释放“为该线程而配置的资源”了。
2. 不要在一个 MFC 程序中使用_beginthreadex() 或 CreateThread()。
3. 如果你写一个多线程程序,而且没有使用 MFC,那么你应该总是和多线程版本的 C Run-time Library 链接,并且总是以 _beginthreadex() 和 _endthreadex() 取代 CreateThread() 和 ExitThread() 。_beginthreadex() 的参数和 CreateThread() 一样,并且承担适度的 C runtime library 初始化工作。
4. 一个程序如果使用多个线程,而不在任何 worker 线程中调用 runtime library,应该能够与单线程版的 runtime library 链接并以 CreateThread() 取代 _beginthreadex() 。然而,“C 程序不调用任何 runtime library 函数”,几乎是不可能的。