Windows 下的多线程应用

Windows 下的多线程中线程锁是通过CRITICAL_SECTION实现,用以保护用户数据,即当串行代码断执行到该部分时,操作系统将告知下一来执行到此处的线程等待,直到上一个线程离开被保护的代码断,从而通过将需要访问数据的部分放在该代码段来保护数据被多线程访问时的一致性。

线程创建

Windows 下可以使用CreateThread(头文件 windows.h)和_beginthreadex(头文件 process.h )来创建线程,针对 C/C++ 的开发,为了安全性考虑,推荐使用_beginthreadex来创建线程。
CreateThread的函数原型如下:

1
2
3
4
5
6
7
8
HANDLEWINAPICreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

函数说明:

  • 第一个参数表示线程内核对象的安全属性,一般传入 NULL 表示使用默认设置。
  • 第二个参数表示线程栈空间大小。传入 0 表示使用默认大小(1MB)。
  • 第三个参数表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
  • 第四个参数是传给线程函数的参数。
  • 第五个参数指定额外的标志来控制线程的创建,为 0 表示线程创建之后立即就可以进行调度,如果为 CREATE_SUSPENDED 则表示线程创建后暂停运行,这样它就无法调度,直到调用 ResumeThread()。
  • 第六个参数将返回线程的 ID 号,传入 NULL 表示不需要返回该线程 ID 号。

函数返回值:
成功返回新线程的句柄,失败返回 NULL。
_beginthreadex的参数与上面一样,实际上_beginthreadex创建线程是会调用 CreateThread,但它通过为线程提供独享的数据块,能够更好的确保 C/C++ 库函数在多线程下的正确执行

线程锁

Windows 下的多线程的线程锁通过CRITICAL_SECTION实现,CRITICAL_SECTION用于定义一个关键段对象,如CRITICAL_SECTION cs;该对象可以记录线程对他的访问,从而使其他线程在访问 cs 时,当 cs 记录到有其他线程还未离开,就一直处理等待之中。类似于 C++ 中的智能指针的实现方式。
CRITICAL_SECTION相关的函数如下(解释为个人理解,仅供参考):

  • InitializeCriticalSection(&cs) 初始化 cs
    EnterCriticalSection(&cs) 该函数会修改 cs,相当于 cs 中有个计数器,初始时为 0(实际上是 -1),当有某个线程执行该函数时,将 cs 中的计数器 +1,如果 cs 的计数器在 +1 之后不为 1,则表示有其他线程在占用这个语句之后的部分,该线程一直等待,直到其值为 1 后开始执行其后语句,并在 cs 中记录这个线程,当这个线程再次进入这个语句时,会直接放行。
    LeaveCriticalSection(&cs) 该函数同样会修改 cs,将 cs 中的计数器减 1,该语句执行后会直接继续往下执行
    DeleteCriticalSection(&cs) 销毁 cs
    所以CRITICAL_SECTION相当于保护了位于EnterCriticalSection()LeaveCriticalSection()之间的一段代码,当该段代码中有对资源的读写访问时,也就起到了相应的资源保护功能。
    这些都在windows.h头文件中

另外两个配合多线程一起使用的函数:
WaitForSingleObject
函数功能:等待函数 – 使线程进入等待状态,直到指定的内核对象被触发
函数原形:

1
2
3
4
DWORDWINAPIWaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);

函数说明:

  • 第一个参数为要等待的内核对象。
  • 第二个参数为最长等待的时间,以毫秒为单位,如传入 5000 就表示 5 秒,传入 0 就立即返回,传入 INFINITE 表示无限等待。

WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
该函数类似,可以用它们让线程等待其他线程都执行完再退出,
handle 参数可以是个包含多个线程句柄的数组
一个多线程的 HelloWorld 程序:
功能:创建 10 个线程,这 10 个线程都执行同一个函数,当然实际开发中不会这么做

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <windows.h>
#include <process.h>

int g_nCount = 0;
const int THREAD_NUM = 10;

CRITICAL_SECTION g_csFunThread;

// Child thread
unsigned int __stdcall ThreadFun(LPVOID pM)
{
EnterCriticalSection(&g_csFunThread);
++g_nCount;
for (size_t i = 0; i < 6; i++)
{
std::cout << "Child Thread ID: " << GetCurrentThreadId() << ", say: " << g_nCount << std::endl;
}
LeaveCriticalSection(&g_csFunThread);
return 0;
}

int main(void)
{
InitializeCriticalSection(&g_csFunThread);
std::cout << "Main Thread" << std::endl;
HANDLE handle[THREAD_NUM];
for (size_t i = 0; i < THREAD_NUM; i++)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, NULL, 0, NULL);
}
/* 当没有这句时将会由于主线程结束导致所有线程都被强制结束 */
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csFunThread);

return 0;
}

参考

  1. https://msdn.microsoft.com/zh-cn/library/ms235302.aspx