Windows线程同步API
本文主要总结创建、结束线程和WIN32 API提供的一些线程同步方法。同步方法包括用户态同步方式:InterLock、CriticalSection、SRWLock和内核态同步方式:Event、Semaphore、Mutex等。本文通过简单的例子演示API的使用,没有包含原理的说明,假定读者具有其他语言或者平台的并发编程经验。
创建、结束线程
WIN32 API虽然提供了CreateThead和ExitThread方法,但是在C++中,永远不应该使用这两个方法创建或结束线程。而应该使用VC++提供的_beginthread、_beginthreadex方法,相应的结束线程方法_endthread、_endthreadex。后者除了在内部调用CreateThread或ExitThread方法外,还负责CRT的初始化或销毁。虽然有直接结束线程的方法,但在C++最好通过线程方法正常返回来结束线程。直接结束线程时C++对象的析构函数不会被调用。
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
|
#include "stdafx.h"
using namespace std;
class Obj
{
public :
Obj() { cout << "Obj() called" << endl; }
~Obj() { cout << "~Obj() called" << endl; }
};
unsigned int WINAPI ThreadFunc( void * pvParam){
cout << static_cast < char *>(pvParam) << endl;
Obj obj;
_endthreadex(2);
return 1;
}
int _tmain( int argc, _TCHAR* argv[])
{
unsigned int threadId;
char *param = "param" ;
HANDLE thread = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, param, 0, &threadId);
Sleep(100);
DWORD exitCode;
GetExitCodeThread( thread , &exitCode);
cout << "ExitCode:" << exitCode << endl;
system ( "pause" );
return 0;
}
|
这段代码的输出为:
param
Obj() called
ExitCode:2
请按任意键继续. . .
_beginthreadex的第一个参数为SECURITY_ATTRIBUTES结构指针,可以指定NULL使用默认的安全配置。第二参数为cbStackSize,线程栈大小;设置为0使用默认值,可以通过链接参数/STACK:[reserve][,commit]控制。第三个参数为线程入口方法地址,方法签名如ThreadFunc所示。第四个三处为传递给入口方法的参数(值传递),具体意义由程序自己解释。最后一个参数是返回的线程ID。返回值为新创建线程的句柄。__endthreadex方法唯一的参数指定线程的ExitCode。可以通过GetExitCodeThread方法获得线程退出码。
InterLocked系列原子方法
InterLocked系列方法可视为原子的。完成其功能时,保证其他线程不会访问同一个资源。例如最简单的InterLockedIncrement方法原子自增一个共享变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
long g_sum(0);
unsigned int WINAPI ThreadFunc( void * pvParam){
for ( int i = 0; i < 100000; ++i)
{
InterlockedIncrement(&g_sum);
}
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
unsigned int threadId;
HANDLE thread1 = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
HANDLE thread2 = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
Sleep(1000);
cout << "Sum:" << g_sum << endl;
system ( "pause" );
return 0;
}
|
其他方法包括:
InterlockedIncrement64:自增一个64位整数。
InterlockedExchangeAdd、InterlockedExchangeAdd64:加和两个数并赋值给第一个值。
InterlockedCompareExchange:比较并交换一个数。
还有很多InterLocked方法,具体参考MSDN文档。
CriticalSection
通过EnterCriticalSection和LeaveCriticalSection方法,可以控制同步一段代码的访问。使用前需要使用InitializeCriticalSection初始化CRITICAL_SECTION。使用方法也很简单。
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
|
CRITICAL_SECTION g_cs;
long g_sum(0);
unsigned int WINAPI ThreadFunc( void * pvParam){
for ( int i = 0; i < 100000; ++i)
{
EnterCriticalSection(&g_cs);
g_sum += 2;
LeaveCriticalSection(&g_cs);
}
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
InitializeCriticalSection(&g_cs);
unsigned int threadId;
HANDLE thread1 = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
HANDLE thread2 = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &threadId);
Sleep(1000);
cout << "Sum:" << g_sum << endl;
DeleteCriticalSection(&g_cs);
system ( "pause" );
return 0;
}
|
这里有一个问题是,如果同步的代码块不是简单g_sum += 2,而是可能抛出异常的复杂代码。就需要确保LeaveCriticalSection一定被调用。不再使用后使用DeleteCriticalSection方法删除之。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class CSManager
{
public :
CSManager(CRITICAL_SECTION *cs) : m_cs(cs)
{
EnterCriticalSection(m_cs);
}
~CSManager()
{
LeaveCriticalSection(m_cs);
}
private :
CRITICAL_SECTION *m_cs;
};
//...
for ( int i = 0; i < 100000; ++i)
{
CSManager CSMgr(&g_cs);
g_sum += 2;
}
//...
|
CSManager在构造函数中调用EnterCriticalSection,析构函数中调用LeaveCriticalSection。保证在代码块结束时调用Leave方法。
另外除了使用阻塞的Enter方法,还有一个TryEnterCriticalSection,该方法尝试进去CriticalSetion,如果失败,不会阻塞,而是立即返回FALSE。
SRWLOCK
SRWLOCK具有和CriticalSection类似的功能。另外还具有读写锁分离的分离的功能。可以使用AcquireSRWLockShared获取共享的读锁。使用AcquireSRWLockExclusive获取独占的写锁。使用对应的ReleaseSRWLockShared/Exclusive方法施放锁。同样地,使用前需要使用InitializeSRWLock初始化。
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
38
|
SRWLOCK g_lock;
long g_sum(0);
unsigned int WINAPI ReadThreadFunc( void * pvParam){
for ( int i = 0; i < 10; ++i)
{
AcquireSRWLockShared(&g_lock);
cout << g_sum << endl;
ReleaseSRWLockShared(&g_lock);
Sleep(1);
}
return 0;
}
unsigned int WINAPI WriteThreadFunc( void * pvParam){
for ( int i = 0; i < 100000; ++i)
{
AcquireSRWLockExclusive(&g_lock);
g_sum += 2;
ReleaseSRWLockExclusive(&g_lock);
}
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
InitializeSRWLock(&g_lock);
unsigned int threadId;
HANDLE thread1 = ( HANDLE )_beginthreadex(NULL, 0, ReadThreadFunc, NULL, 0, &threadId);
HANDLE thread2 = ( HANDLE )_beginthreadex(NULL, 0, ReadThreadFunc, NULL, 0, &threadId);
HANDLE thread3 = ( HANDLE )_beginthreadex(NULL, 0, WriteThreadFunc, NULL, 0, &threadId);
Sleep(1000);
cout << "Sum:" << g_sum << endl;
system ( "pause" );
return 0;
}
|
SRWLOCK不具备类似于TryEnterCriticalSection的非阻塞方法。大多数情况下,SRWLOCK比CRITICAL_SECTION有更好的性能。
Condition Variable
为实现近点的生产者消费者问题。我们可以使用两个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唤醒一个等待的线程。
和Condition Variable配置使用的可以使CrticalSection也可以使SRWLock。
1
2
3
4
5
6
7
8
9
10
|
BOOL SleepConditionVariableCS(
PCONDITION_VARIABLE pConditionVariable,
PCRITICAL_SECTION pCriticalSection,
DWORD dwMilliseconds);
BOOL SleepConditionVariableSRW(
PCONDITION_VARIABLE pConditionVariable,
PSRWLOCK pSRWLock,
DWORD dwMilliseconds,
ULONG Flags);
|
参数dwMilliseconds指定等待超时的时间,如果超时方法返回FASLE;INFINITE指定等待不超时。参数Flags指定被唤醒时尝试获得的锁的类型。CONDITION_VARIABLE_LOCKMODE_ SHARED指定获得共享锁或者0指定获得独占锁。
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
const int MAX_SIZE = 10;
CONDITION_VARIABLE g_full;
CONDITION_VARIABLE g_empty;
SRWLOCK g_lock;
list<Product> products;
unsigned int WINAPI ProduceThreadFunc( void * pvParam)
{
int i(0);
while ( true )
{
Sleep( rand () % 100);
AcquireSRWLockExclusive(&g_lock);
if (products.size() >= MAX_SIZE)
{
SleepConditionVariableSRW(&g_full, &g_lock, INFINITE, 0);
}
else
{
cout << "Produce Product:" << i << " by thread " << GetThreadId(GetCurrentThread()) << endl;
products.push_back(Product(i++));
}
WakeAllConditionVariable(&g_empty);
ReleaseSRWLockExclusive(&g_lock);
}
return 0;
}
unsigned int WINAPI ConsumeThreadFunc( void * pvParam)
{
while ( true )
{
Sleep( rand () % 100);
AcquireSRWLockExclusive(&g_lock);
if (products.size() == 0)
{
SleepConditionVariableSRW(&g_empty, &g_lock, INFINITE, 0);
}
else
{
Product p = products.front();
products.pop_front();
cout << "Consume Product:" << p.m_no << " by thread " << GetThreadId(GetCurrentThread()) << endl;
}
WakeAllConditionVariable(&g_full);
ReleaseSRWLockExclusive(&g_lock);
}
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
srand ((unsigned) time (NULL));
InitializeSRWLock(&g_lock);
unsigned int threadId;
HANDLE thread1 = ( HANDLE )_beginthreadex(NULL, 0, ProduceThreadFunc, NULL, 0, &threadId);
HANDLE thread2 = ( HANDLE )_beginthreadex(NULL, 0, ConsumeThreadFunc, NULL, 0, &threadId);
HANDLE thread3 = ( HANDLE )_beginthreadex(NULL, 0, ConsumeThreadFunc, NULL, 0, &threadId);
WaitForSingleObject(thread1, INFINITE);
WaitForSingleObject(thread2, INFINITE);
WaitForSingleObject(thread3, INFINITE);
system ( "pause" );
return 0;
}
|
内核态线程同步方法
除了上面介绍的用户态的线程同步方法。本文继续通过几个简单例子演示内核态的线程同步方法的使用。内核态线程同步方法在性能上肯定比用户态同步方法要差很多。但可以在多个进程间共享。
创建所有的内核态同步对象都范围一个内核对象句柄HANDLE。通过WaitForSingleObject或者WaitForMultipleObjects等待内核同步对象转换为已传信状态(signaled)。如果等待的是线程或者进程对象,那么对应线程或进程结束后即转换为已传信状态。同时还可以指定一个超时时间。WaitForSingleObject包括WAIT_OBJECT_0,WAIT_TIMEOUT和WAIT_FAILED。不再使用后调用CloseHandle释放引用。
1
2
3
4
5
6
7
8
9
10
11
12
|
DWORD dw = WaitForSingleObject(hProcess, 5000);
switch (dw) {
case WAIT_OBJECT_0:
// The process terminated.
break ;
case WAIT_TIMEOUT:
// The process did not terminate within 5000 milliseconds.
break ;
case WAIT_FAILED:
// Bad call to function (invalid handle?)
break ;
}
|
WaitForMultipleObjects如果指定参数bWaitAll为TRUE,则等待所有对象都转换为已传信状态后才返回,如果为指定bWaitAll为FALSE,则任意对象转换为已传信状态即返回。可以通过以下方法来判断是那个内核同步对象。
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
|
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 ;
}
|
Event
Event语义上可以理解为一个事件是否发生。SetEvent方法设置Event为Signaled状态。Event有两种类型。第一种是自动重置的事件,调用SetEvent方法后,唤醒一个等待的线程后即自动转换为未传信状态。第二种是手动重置事件,调用SetEvent方法后,需要调用ResetEvent方法设置事件为未传信状态。PulseEvent相当于调用SetEvent后立即调用ResetEvent。对于手动重置时间,PulseEvent会唤醒所有等待的线程。而对于自动重置的事件PulseEvent只会唤醒一个等待的线程。
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
|
HANDLE g_taskEvent;
unsigned int WINAPI ComputationTask( void * pvParam)
{
WaitForSingleObject(g_taskEvent, INFINITE);
for ( int i = 0; i < 10; ++i)
{
cout << "comput " << i << endl;
}
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
g_taskEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
unsigned int threadId;
HANDLE thread1 = ( HANDLE )_beginthreadex(NULL, 0, ComputationTask, NULL, 0, &threadId);
system ( "pause" );
SetEvent(g_taskEvent);
ResetEvent(g_taskEvent);
WaitForSingleObject(thread1, INFINITE);
system ( "pause" );
return 0;
}
|
上面是一个简单的例子,ComputationTask线程等待用户输入后才开始计算任务。
Semaphore
Semaphore维护一个资源计数count和一个最大计数maxCount。
当count大于0时,semaphore处于已传信状态。
当count等于0是,semaphore处于未传信状态。
通过ReleaseSemaphore增加count计数,WaitForSingleObject减少cout计数。count不会小于0,也不能大于maxCount。
例如,可以使用semaphore控制能够同时处理的最大任务线程数。当有超过最大数的更多任务线程开启时只能等待其他任务完成并调用ReleaseSemaphore方法施放资源引用计数。
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
|
HANDLE g_semaphore;
unsigned int WINAPI RequstProcesor( void * pvParam)
{
WaitForSingleObject(g_semaphore, INFINITE);
cout << "Start process request " << GetThreadId(GetCurrentThread()) << endl;
Sleep(1000);
ReleaseSemaphore(g_semaphore, 1, NULL);
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
g_semaphore = CreateSemaphore(NULL, 2, 2, NULL);
HANDLE threads[10];
for ( int i = 0; i < 10; i++)
{
threads[i] = ( HANDLE )_beginthreadex(NULL, 0, RequstProcesor, NULL, 0, NULL);
}
WaitForMultipleObjects(10, threads, TRUE, INFINITE);
system ( "pause" );
return 0;
}
|
上面的代码,启动了10个线程,但只能有2个现场可以同时执行,更多的线程只能等待。
Mutex
mutex的功能和CriticalSection功能很相似。都是控制一段临界代码的互斥访问。通过WaitForSingleObject等待mutex。ReleaseMutex释放mutex。
mutex维护一个threadId和一个使用计数count。如果CreateMutex的参数bInitialOwner为TRUE,这threadId为调用线程,cout为1。否则都初始为0。
如果threadId为0,mutex没有被任何线程使用,处于已传信状态。如果threadId不为0,mutex处于未传信状态。mutex和其他内核同步对象一个不同的特殊地方在于。即时mutex处于未传信状态。如果调用WaitForSingleObject的线程是mutex的threadId对应的线程,WaitForSingleObject不会阻塞相当于处于已传信状态。下面的例子演示了mutex的使用。
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
|
HANDLE g_mutex;
void ProcessA()
{
WaitForSingleObject(g_mutex, INFINITE);
cout << "ProcessA" << " by thread " << GetThreadId(GetCurrentThread()) << endl;
ReleaseMutex(g_mutex);
}
void ProcessB()
{
WaitForSingleObject(g_mutex, INFINITE);
ProcessA();
cout << "ProcessB" << " by thread " << GetThreadId(GetCurrentThread()) << endl;
ReleaseMutex(g_mutex);
}
unsigned int WINAPI ThreadFunc( void * pvParam)
{
ProcessB();
return 0;
}
int _tmain( int argc, _TCHAR* argv[])
{
g_mutex = CreateMutex(NULL, FALSE, NULL);
HANDLE threads[10];
for ( int i = 0; i < 10; i++)
{
threads[i] = ( HANDLE )_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, NULL);
}
WaitForMultipleObjects(10, threads, TRUE, INFINITE);
system ( "pause" );
return 0;
}
|
Windows线程同步API相关推荐
- Windows线程同步、CreateThread与_beginthread的区别
进程与线程的区别 一个exe是可执行程序文件,文件被系统加载到内存并将相关资源(比如内存.文件)准备好,并在内存中开始运行后,此时叫做一个进程.进程进行的是负责与操作系统资源的协调,进程所需要的资源在 ...
- Windows线程同步机制的区别与比较及进程通信方法
原文:http://blog.csdn.net/eulb/article/details/2177500 多线程同步机制 (Windows) 线程的同步机制: 1. Event 用事件(Event ...
- Linux线程同步与Windows线程同步
简介 线程同步概念:在多线程下,在一段时间内只允许一个线程访问资源,不允许其它线程访问. 在WIN32中,同步机制主要有以下几种: (1)事件(Event); (2)信号量(semaphore); ( ...
- windows线程同步-原子操作-Interlocked系列函数(用户模式)
Interlocked系列函数用来保证原子访问. InterlockedExchangeAdd提供保证long类型的原子操作. InterlockedExchangeAdd64提供long long ...
- Windows线程同步--关键段和旋转锁
关键段 关键段(Critical Section)是一小段代码,它在执行之前需要独占对一些共享资源的访问权.这种方式可以让多行代码以"原子方式"对资源进行操控.这里的原子方式,指的 ...
- 《Windows via C/C++》学习笔记 —— “线程同步”之“检测死锁”
本来这篇内容在书中是在"其他线程同步函数"这一节中的.这节中介绍了另外的几个等待函数,比如WaitForInputIdle.MsgWaitForMultipleObjects.Wa ...
- WINDOWS下线程同步探讨
本文主要讨论WINDOWS应用层编程的线程同步问题.在实际编程过程中,我们经常会遇到线程同步的问题,例如在编写多线程共同访问一个共享资源的程序时,如果多个线程只是读取资源那么就不会涉及到下面我们要讨论 ...
- C++线程同步的四种方式(Windows)
原文链接: https://blog.csdn.net/s_lisheng/article/details/74278765 什么是鲜橙同步?为什么要进行线程同步? 在程序中使用多线程时,一般情况, ...
- windows下线程同步的常见方法:CreateEvent和SetEvent及WaitForSingleObject windows下常见锁的实现 EnterCriticalSection
windows下我们常用的线程同步的方法是 事件 首先介绍CreateEvent是创建windows事件的意思,作用主要用在判断线程退出,程锁定方面. CreateEvent 函功能描述:创建或打 ...
最新文章
- tomcat高并发的配置
- 深度学习实现场景字符识别模型|代码干货
- leetcode004 Median_of_Two_Sorted_Arrays.py
- 华为平板电脑_当5G遇上平板电脑,华为MatePad Pro 5G带来了什么?
- 实验五 oracle高级数据查询技术
- bs4配合上re正则表达式
- uva11111 	Generalized Matrioshkas
- JavaScript基础14-day16【事件委派、事件绑定、事件传播、滚轮事件、键盘事件、键盘移动div】
- MySQL分组查询—按函数分组
- awk教程入门与实例练习(一)
- java一年制培训_学Java学了一年,怎么找实习?
- NCFM识别-Googlenet
- 谁抢光了你的火车票?
- png、jpg图片格式的区别及
- yytextview 复制_YYText使用篇(一)
- Windows11 Store应用商店下载的软件,怎么创建快捷方式
- Go生成Excel文件并下载及问题
- 比较两组数据的差异用什么图更直观_用好这11种可视化图表,数据可视化技能秒提升...
- python拼音怎么写-Python汉字转换成拼音
- 【word】word表格不自动跨页,文字显示不全