Windows线程同步API

转载自:http://www.cnblogs.com/devj/archive/2011/10/25/2223856.html

神作,很全面。需要注意的是,部分内容不适合Vista以下版本的操作系统,不过我已经加了注释标志。

本文主要总结创建、结束线程和WIN32 API提供的一些线程同步方法。同步方法包括用户态同步方式:InterLock、CriticalSection、SRWLock和内核态同步方式:Event、semaphore、Mutex等。本文通过简单的例子演示API的使用,没有包含原理的说明,假定读者具有其他语言或者平台的并发编程经验。

创建、结束线程

WIN32 API虽然提供了CreateThead和ExitThread方法,但是在C++中,永远不应该使用这两个方法创建或结束线程;而应该使用VC++提供的创建线程方法:_beginthread()、_beginthreadex()方法,相应的结束线程方法_endthread()、_endthreadex()。

后者除了在内部调用CreateThread()或ExitThread()方法外,还负责CRT的初始化或销毁。虽然有直接结束线程的方法,但在C++最好通过线程方法正常返回来结束线程。直接结束线程时C++对象的析构函数不会被调用

(CRT: C runtime library(part of the C standard library),C语言运行时库)

01 #include "stdafx.h"
02
03 using namespace std;
04
05 class Obj
06 {
07 public:
08 Obj() { cout << "Obj() called" << endl; }
09 ~Obj() { cout << "~Obj() called" << endl; }
10 };
11
12 unsigned int WINAPI ThreadFunc(void* pvParam){
13 cout << static_cast<char*>(pvParam) << endl;
14 Obj obj;
15 _endthreadex(2);
16 return 1;
17 }
18
19 int _tmain(int argc, _TCHAR* argv[])
20 {
21 unsigned int threadId;
22 char *param = "param";
23 HANDLE thread = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, param, 0, &threadId);
24
25 Sleep(100);
26 DWORD exitCode;
27 GetExitCodeThread(thread, &exitCode);
28 cout << "ExitCode:" << exitCode << endl;
29 system("pause");
30 return 0;

31 }

这段代码的输出为:

param

Obj() called
ExitCode:2

请按任意键继续. . .

_beginthreadex的函数参数解析:

第一个参数为SECURITY_ATTRIBUTES结构指针,可以指定NULL使用默认的安全配置;

第二参数为cbStackSize,线程栈大小;设置为0使用默认值,可以通过链接参数/STACK:[reserve][,commit]控制;

第三个参数为线程入口方法地址,方法签名如ThreadFunc所示;

第四个三处为传递给入口方法的参数(值传递),具体意义由程序自己解释;

最后一个参数是返回的线程ID;

返回值为新创建线程的句柄。__endthreadex方法唯一的参数指定线程的ExitCode。可以通过GetExitCodeThread方法获得线程退出码。

用户模式下的线程同步:

(1) InterLocked系列原子方法

InterLocked系列方法可视为原子的。完成其功能时,保证其他线程不会访问同一个资源。例如最简单的InterLockedIncrement方法原子自增一个共享变量。

01 long g_sum(0);
02
03 unsigned int WINAPI ThreadFunc(void* pvParam){
04 for(int i = 0; i < 100000; ++i)
05 {
06 InterlockedIncrement(&g_sum);
07 }
08 return 0;
09 }
10
11 int _tmain(int argc, _TCHAR* argv[])
12 {
13 unsigned int threadId;
14 HANDLE thread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
15 HANDLE thread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
16
17 Sleep(1000);
18 cout << "Sum:" << g_sum << endl;
19 system("pause");
20 return 0;

21 }

其他方法包括:

InterlockedIncrement64:自增一个64位整数。

InterlockedExchangeAdd、InterlockedExchangeAdd64:加和两个数并赋值给第一个值。

InterlockedCompareExchange:比较并交换一个数。

还有很多InterLocked方法,具体参考MSDN文档。

(2) CriticalSection

使用前需要定义结构体CRITICAL_SECTION,使用InitializeCriticalSection()初始化CRITICAL_SECTION变量;

通过EnterCriticalSection()和LeaveCriticalSection()方法,可以控制同步一段代码的访问;

使用结束以后,用DeleteCriticalSection()删除临界区;

01 CRITICAL_SECTION g_cs;
02 long g_sum(0);
03
04 unsigned int WINAPI ThreadFunc(void* pvParam){
05 for(int i = 0; i < 100000; ++i)
06 {
07 EnterCriticalSection(&g_cs);
08 g_sum += 2;
09 LeaveCriticalSection(&g_cs);
10 }
11 return 0;
12 }
13
14 int _tmain(int argc, _TCHAR* argv[])
15 {
16 InitializeCriticalSection(&g_cs);
17 unsigned int threadId;
18 HANDLE thread1 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
19 HANDLE thread2 = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
20
21 Sleep(1000);
22 cout << "Sum:" << g_sum << endl;
23 DeleteCriticalSection(&g_cs);
24 system("pause");
25 return 0;

26 }

这里有一个问题是,如果同步的代码块不是简单g_sum += 2,而是可能抛出异常的复杂代码。就需要确保LeaveCriticalSection一定被调用。不再使用后使用DeleteCriticalSection方法删除之。

01 class CSManager
02 {
03 public:
04 CSManager(CRITICAL_SECTION *cs) : m_cs(cs)
05 {
06 EnterCriticalSection(m_cs);
07 }
08 ~CSManager()
09 {
10 LeaveCriticalSection(m_cs);
11 }
12 private:
13 CRITICAL_SECTION *m_cs;
14 };
15
16 //...
17 for(int i = 0; i < 100000; ++i)
18 {
19 CSManager CSMgr(&g_cs);
20 g_sum += 2;
21 }

22 //...

CSManager在构造函数中调用EnterCriticalSection,析构函数中调用LeaveCriticalSection。保证在代码块结束时调用Leave方法。

另外除了使用阻塞的Enter方法,还有一个TryEnterCriticalSection,该方法尝试进去CriticalSetion,如果失败,不会阻塞,而是立即返回FALSE。

(3) SRWLOCK

SRWLOCK具有和CriticalSection类似的功能。另外还具有读写锁分离的分离的功能。

使用前需要使用InitializeSRWLock()初始化。

可以使用AcquireSRWLockShared()获取共享的读锁;使用AcquireSRWLockExclusive()获取独占的写锁。

使用对应的ReleaseSRWLockShared/Exclusive方法施放锁;

注意:

SRWLOCK需要的条件如下:

操作系统:最低为Vista;

所需头文件:Winbase.h (include Windows.h);

需要静态库:kernal32.lib;

需要动态库:kernal32.dll;

01 SRWLOCK g_lock;
02 long g_sum(0);
03
04 unsigned int WINAPI ReadThreadFunc(void* pvParam){
05 for(int i = 0; i < 10; ++i)
06 {
07 AcquireSRWLockShared(&g_lock);
08 cout << g_sum << endl;
09 ReleaseSRWLockShared(&g_lock);
10 Sleep(1);
11 }
12 return 0;
13 }
14
15 unsigned int WINAPI WriteThreadFunc(void* pvParam){
16 for(int i = 0; i < 100000; ++i)
17 {
18 AcquireSRWLockExclusive(&g_lock);
19 g_sum += 2;
20 ReleaseSRWLockExclusive(&g_lock);
21 }
22 return 0;
23 }
24
25 int _tmain(int argc, _TCHAR* argv[])
26 {
27 InitializeSRWLock(&g_lock);
28
29 unsigned int threadId;
30 HANDLE thread1 = (HANDLE)_beginthreadex(NULL, 0, ReadThreadFunc, NULL, 0, &threadId);
31 HANDLE thread2 = (HANDLE)_beginthreadex(NULL, 0, ReadThreadFunc, NULL, 0, &threadId);
32 HANDLE thread3 = (HANDLE)_beginthreadex(NULL, 0, WriteThreadFunc, NULL, 0, &threadId);
33
34 Sleep(1000);
35 cout << "Sum:" << g_sum << endl;
36 system("pause");
37 return 0;

38 }

SRWLOCK不具备类似于TryEnterCriticalSection的非阻塞方法

大多数情况下,SRWLOCK比CRITICAL_SECTION有更好的性能。

(4) Condition Variable
Condition Variable跟CrticalSection配合使用

BOOL SleepConditionVariableCS(

PCONDITION_VARIABLE pConditionVariable,

PCRITICAL_SECTION pCriticalSection,

DWORD dwMilliseconds);

跟SRWLock配合使用:

BOOL SleepConditionVariableSRW(

PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWORD dwMilliseconds,

ULONG Flags);

其中,参数dwMilliseconds指定等待超时的时间,如果超时方法返回FASLE;INFINITE指定等待不超时。

参数Flags指定被唤醒时尝试获得的锁的类型,取值为CONDITION_VARIABLE_LOCKMODE_ SHARED指定获得共享锁;取值为0时,指定获得独占锁。

注意:

Condition Variable需要的条件如下:

操作系统:最低为Vista;

所需头文件:Winbase.h (include Windows.h);

需要静态库:kernal32.lib;

需要动态库:kernal32.dll;


为实现近点的生产者消费者问题。我们可以使用两个CONDITION_VARIABLE:g_full,g_empty来实现。

在缓冲区满的时候,生产者线程调用SleepConditionVariableSRW(&g_full, &g_lock, INFINITE, 0)施放获得的锁并等待g_full;

缓冲区空的时候,消费者可以调用leepConditionVariableSRW(&g_empty, &g_lock, INFINITE, 0)施放获得的锁并等待g_empty;

条件满足后,可是使用WakeAllConditionVariable()唤醒所有等待的线程或者使用WakeConditionVariable()唤醒一个等待的线程。

01 const int MAX_SIZE = 10;
02
03 CONDITION_VARIABLE g_full;
04 CONDITION_VARIABLE g_empty;
05 SRWLOCK g_lock;
06
07 list<Product> products;
08
09 unsigned int WINAPI ProduceThreadFunc(void* pvParam)
10 {
11 int i(0);
12 while(true)
13 {
14 Sleep(rand() % 100);
15 AcquireSRWLockExclusive(&g_lock);
16
17 if (products.size() >= MAX_SIZE)
18 {
19 SleepConditionVariableSRW(&g_full, &g_lock, INFINITE, 0);
20 }
21 else
22 {
23 cout << "Produce Product:" << i << " by thread " << GetThreadId(GetCurrentThread()) << endl;
24 products.push_back(Product(i++));
25 }
26
27 WakeAllConditionVariable(&g_empty);
28 ReleaseSRWLockExclusive(&g_lock);
29
30 }
31 return 0;
32 }
33
34 unsigned int WINAPI ConsumeThreadFunc(void* pvParam)
35 {
36 while(true)
37 {
38 Sleep(rand() % 100);
39
40 AcquireSRWLockExclusive(&g_lock);
41 if(products.size() == 0)
42 {
43 SleepConditionVariableSRW(&g_empty, &g_lock, INFINITE, 0);
44 }
45 else
46 {
47 Product p = products.front();
48 products.pop_front();
49 cout << "Consume Product:" << p.m_no << " by thread " << GetThreadId(GetCurrentThread()) << endl;
50 }
51
52 WakeAllConditionVariable(&g_full);
53 ReleaseSRWLockExclusive(&g_lock);
54
55
56 }
57 return 0;
58 }
59
60 int _tmain(int argc, _TCHAR* argv[])
61 {
62 srand((unsigned)time(NULL));
63 InitializeSRWLock(&g_lock);
64 unsigned int threadId;
65 HANDLE thread1 = (HANDLE)_beginthreadex(NULL, 0, ProduceThreadFunc, NULL, 0, &threadId);
66 HANDLE thread2 = (HANDLE)_beginthreadex(NULL, 0, ConsumeThreadFunc, NULL, 0, &threadId);
67 HANDLE thread3 = (HANDLE)_beginthreadex(NULL, 0, ConsumeThreadFunc, NULL, 0, &threadId);
68
69 WaitForSingleObject(thread1, INFINITE);
70 WaitForSingleObject(thread2, INFINITE);
71 WaitForSingleObject(thread3, INFINITE);
72 system("pause");
73 return 0;
74 }

内核态线程同步方法

除了上面介绍的用户态的线程同步方法。本文继续通过几个简单例子演示内核态的线程同步方法的使用。

内核态线程同步方法在性能上肯定比用户态同步方法要差很多。但可以在多个进程间共享。

创建所有的内核态同步对象都返回一个内核对象句柄HANDLE;

通过WaitForSingleObject或者WaitForMultipleObjects等待内核同步对象转换为有信号状态(signaled)。(如果等待的是线程或者进程对象,那么对应线程或进程结束后即转换为有信号状态)同时还可以指定一个超时时间。

不再使用同步对象后调用CloseHandle()释放引用。

WaitForSingleObject()的返回结果包括WAIT_OBJECT_0,WAIT_TIMEOUT和WAIT_FAILED;

01 DWORD dw = WaitForSingleObject(hProcess, 5000);
02 switch (dw) {
03 case WAIT_OBJECT_0:
04 // The process terminated.
05 break;
06 case WAIT_TIMEOUT:
07 // The process did not terminate within 5000 milliseconds.
08 break;
09 case WAIT_FAILED:
10 // Bad call to function (invalid handle?)
11 break;

12 }

WaitForMultipleObjects()如果指定参数bWaitAll为TRUE,则等待所有对象都转换为已传信状态后才返回,如果为指定bWaitAll为FALSE,则任意对象转换为已传信状态即返回。可以通过以下方法来判断是那个内核同步对象。

h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch (dw) {
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break;
case WAIT_TIMEOUT:
// None of the objects became signaled within 5000 milliseconds.
break;
case WAIT_OBJECT_0 + 0:
// The process identified by h[0] (hProcess1) terminated.
break;
case WAIT_OBJECT_0 + 1:
// The process identified by h[1] (hProcess2) terminated.
break;
case WAIT_OBJECT_0 + 2:
// The process identified by h[2] (hProcess3) terminated.
break;
}

(1) Event

Event语义上可以理解为一个事件是否发生。SetEvent()方法设置Event为Signaled状态。

Event有两种类型。第一种是自动重置的事件,调用SetEvent方法后,唤醒一个等待的线程后即自动转换为无信号状态;第二种是手动重置事件,调用SetEvent方法后,需要调用ResetEvent方法设置事件为无信号状态。

PulseEvent相当于调用SetEvent后立即调用ResetEvent。对于手动重置时间,PulseEvent会唤醒所有等待的线程。而对于自动重置的事件PulseEvent只会唤醒一个等待的线程。

01 HANDLE g_taskEvent;
02
03 unsigned int WINAPI ComputationTask(void* pvParam)
04 {
05 WaitForSingleObject(g_taskEvent, INFINITE);
06 for(int i = 0; i < 10; ++i)
07 {
08 cout << "comput " << i << endl;
09 }
10 return 0;
11 }
12
13 int _tmain(int argc, _TCHAR* argv[])
14 {
15 g_taskEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
16 unsigned int threadId;
17 HANDLE thread1 = (HANDLE)_beginthreadex(NULL, 0, ComputationTask, NULL, 0, &threadId);
18
19 system("pause");
20 SetEvent(g_taskEvent);
21 ResetEvent(g_taskEvent);
22
23 WaitForSingleObject(thread1, INFINITE);
24 system("pause");
25 return 0;

26 }

上面是一个简单的例子,ComputationTask线程等待用户输入后才开始计算任务。

(2) Semaphore

Semaphore维护一个资源计数count和一个最大计数maxCount。

当count大于0时,semaphore处于有信号状态;当count等于0是,semaphore处于无信号状态。

通过ReleaseSemaphore增加count计数,WaitForSingleObject减少cout计数。count不会小于0,也不能大于maxCount。

例如,可以使用semaphore控制能够同时处理的最大任务线程数。当有超过最大数的更多任务线程开启时只能等待其他任务完成并调用ReleaseSemaphore方法施放资源引用计数。

01 HANDLE g_semaphore;
02
03 unsigned int WINAPI RequstProcesor(void* pvParam)
04 {
05 WaitForSingleObject(g_semaphore, INFINITE);
06 cout << "Start process request " << GetThreadId(GetCurrentThread()) << endl;
07 Sleep(1000);
08 ReleaseSemaphore(g_semaphore, 1, NULL);
09 return 0;
10 }
11
12 int _tmain(int argc, _TCHAR* argv[])
13 {
14
15 g_semaphore = CreateSemaphore(NULL, 2, 2, NULL);
16
17 HANDLE threads[10];
18 for(int i = 0; i < 10; i++)
19 {
20 threads[i] = (HANDLE)_beginthreadex(NULL, 0, RequstProcesor, NULL, 0, NULL);
21 }
22
23 WaitForMultipleObjects(10, threads, TRUE, INFINITE);
24 system("pause");
25 return 0;

26 }

上面的代码,启动了10个线程,但只能有2个现场可以同时执行,更多的线程只能等待。

(3) Mutex

mutex的功能和CriticalSection功能很相似。都是控制一段临界代码的互斥访问。

通过WaitForSingleObject()等待mutex,直到mutex变为有信号状态;通过ReleaseMutex()释放mutex,使之变为有信号状态。

mutex维护一个threadId和一个使用计数count。如果CreateMutex的参数bInitialOwner为TRUE,这threadId为调用线程,cout为1。否则都初始为0。

如果threadId为0,mutex没有被任何线程使用,处于有信号状态。如果threadId不为0,mutex处于无信号状态。

mutex和其他内核同步对象一个不同之处:即使mutex处于无信号状态,如果调用WaitForSingleObject()的线程是mutex的threadId对应的线程,(即自己的mutex本身有信号,再重新调用WaitForSingleObject()函数),WaitForSingleObject()不会阻塞,相当于处于有信号状态。

下面的例子演示了mutex的使用。

01 HANDLE g_mutex;
02
03 void ProcessA()
04 {
05 WaitForSingleObject(g_mutex, INFINITE);
06 cout << "ProcessA" << " by thread " << GetThreadId(GetCurrentThread()) << endl;
07 ReleaseMutex(g_mutex);
08 }
09
10 void ProcessB()
11 {
12 WaitForSingleObject(g_mutex, INFINITE);
13 ProcessA();
14 cout << "ProcessB" << " by thread " << GetThreadId(GetCurrentThread()) << endl;
15 ReleaseMutex(g_mutex);
16 }
17
18 unsigned int WINAPI ThreadFunc(void* pvParam)
19 {
20 ProcessB();
21 return 0;
22 }
23
24 int _tmain(int argc, _TCHAR* argv[])
25 {
26 g_mutex = CreateMutex(NULL, FALSE, NULL);
27
28 HANDLE threads[10];
29 for(int i = 0; i < 10; i++)
30 {
31 threads[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);
32 }
33
34 WaitForMultipleObjects(10, threads, TRUE, INFINITE);
35 system("pause");
36 return 0;
37 }

你可能感兴趣的:(thread,windows,Semaphore,null,System,winapi)