C++ 多线程之间的通信方式(windows下实现)

最近学习了C++ 多线程之间的通信方式,记录一下。

参考原文:https://blog.csdn.net/eulb/article/details/2177500

C++线程的通信方式有很多种,这里记录一下常用的几种:

1.全局变量

2.互斥量

3.信号量

4.事件

5.临界区



1.全局变量

通过全局变量进行通信,要对该变量加关键字volatile
volatile(易变的):每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取

#include 
#include 

//全局变量
volatile int signalNum = 0; 

DWORD WINAPI threadFuncA(LPVOID lpParamter)
{
	Sleep(2000);
	if (0 == signalNum)
	{
		printf("signalNum not changed!\n");
	}
	else
	{
		printf("signalNum has changed!\n");
	}

	return 0;
}

DWORD WINAPI threadFuncB(LPVOID lpParamter)
{
	signalNum = 2;
	return 0;
}

int main()
{
	HANDLE threadA = CreateThread(NULL, 0, threadFuncA, NULL, 0, NULL);
	HANDLE threadB = CreateThread(NULL, 0, threadFuncB, NULL, 0, NULL);
	
	WaitForSingleObject(threadA, INFINITE);
	CloseHandle(threadA);//CloseHandle只是关闭了系统句柄,该线程还是可以正常的运行
	CloseHandle(threadB);
	return 0;
}

执行结果:



2.互斥量

互斥器的功能和临界区域很相似。区别是:Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象(Event、Semaphore也是),可以跨进程使用,而且等待一个被锁住的Mutex可以设定 TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。MFC中的对应类为CMutex。Win32函数有:创建互斥体CreateMutex() ,打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex的拥有权并非属于那个产生它的线程,而是最后那个对此 Mutex进行等待操作(WaitForSingleObject等等)并且尚未进行ReleaseMutex()操作的线程。线程拥有Mutex就好像进入Critical Section一样,一次只能有一个线程拥有该Mutex。如果一个拥有Mutex的线程在返回之前没有调用ReleaseMutex(),那么这个 Mutex就被舍弃了,但是当其他线程等待(WaitForSingleObject等)这个Mutex时,仍能返回,并得到一个 WAIT_ABANDONED_0返回值。能够知道一个Mutex被舍弃是Mutex特有的。

#include 
#include 

/*
通过互斥量实现线程间的同步,初始化为没有加锁的状态
*/
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);

DWORD WINAPI threadFuncA(LPVOID lpParamter)
{
	//对互斥量加锁,如果已经加锁了则等待其解锁,等待时间为INFINITE(表示永久)
	WaitForSingleObject(mutex, INFINITE);
	/*
		WaitForSingleObject(mutex, INFINITE);
		
		这里可以做对共享资源的操作
		
		ReleaseMutex(mutex);
	*/
	printf("threadFuncA lock mutex,please wait~~~~~~\n");
	Sleep(5000);
	printf("threadFuncA unlock mutex!!!!!!!!!!!!!!!!\n");
	//互斥量解锁
	ReleaseMutex(mutex);
	return 0;
}

DWORD WINAPI threadFuncB(LPVOID lpParamter)
{
	Sleep(2000);
	WaitForSingleObject(mutex, INFINITE);
	printf("This is threadFuncB ~~~~~~\n");
	ReleaseMutex(mutex);
	return 0;
}

int main()
{
	HANDLE threadA = CreateThread(NULL, 0, threadFuncA, NULL, 0, NULL);
	HANDLE threadB = CreateThread(NULL, 0, threadFuncB, NULL, 0, NULL);
	
	WaitForSingleObject(threadB, INFINITE);
	CloseHandle(threadA);//CloseHandle只是关闭了系统句柄,该线程还是可以正常的运行
	CloseHandle(threadB);
	return 0;
}

执行结果:



3.信号量

信号量是最具历史的同步机制。信号量是解决producer/consumer问题的关键要素。对应的MFC类是Csemaphore。Win32函数 CreateSemaphore()用来产生信号量。ReleaseSemaphore()用来解除锁定。Semaphore的现值代表的意义是目前可用的资源数,如果Semaphore的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore现值不为0,Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当时不会超过初始设定的资源总数。

#include 
#include 

/*
使用信号量
参数2:当前可用的信号的量个数 范围为(0 - 参数3)
参数3:信号量的最大值
*/
HANDLE signalSemaphore = CreateSemaphore(NULL, 1, 1, NULL);

DWORD WINAPI threadFuncA(LPVOID lpParamter)
{
	Sleep(2000);
	WaitForSingleObject(signalSemaphore, INFINITE);
	printf("threadFuncA 使用了一个信号量!\n");
	Sleep(2000);
	printf("threadFuncA 释放了一个信号量!\n");
	ReleaseSemaphore(signalSemaphore, 1, NULL);
	return 0;
}

DWORD WINAPI threadFuncB(LPVOID lpParamter)
{
	//这里会使信号量减1
	WaitForSingleObject(signalSemaphore, INFINITE);
	printf("threadFuncB 使用了一个信号量!\n");
	Sleep(5000);
	//信号量+1
	printf("threadFuncB 释放了一个信号量!\n");
	ReleaseSemaphore(signalSemaphore, 1, NULL);
	return 0;
}

int main()
{
	HANDLE threadA = CreateThread(NULL, 0, threadFuncA, NULL, 0, NULL);
	HANDLE threadB = CreateThread(NULL, 0, threadFuncB, NULL, 0, NULL);
	
	WaitForSingleObject(threadA, INFINITE);
	CloseHandle(threadA);//CloseHandle只是关闭了系统句柄,该线程还是可以正常的运行
	CloseHandle(threadB);

	return 0;
}

执行结果:C++ 多线程之间的通信方式(windows下实现)_第1张图片



4.事件
用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。MFC中对应的类为 CEvent.。CEvent的构造函数默认创建一个自动重置的事件,而且处于未激发状态。共有三个函数来改变事件的状态:SetEvent,ResetEvent和PulseEvent。用事件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件调用SetEvent和PulseEvent有可能会引起死锁,必须小心。

#include 
#include 

//通过事件进行通信
/*创建事件CreateEvent
LPSECURITY_ATTRIBUTESlpEventAttributes,// 安全属性
BOOLbManualReset,// 复位方式,如果是TRUE,那么必须用ResetEvent函数来复原到无信号状态。FALSE自动将事件状态复原为无信号状态。
BOOLbInitialState,// 初始状态,TRUE有信号,FALESE无信号
LPCTSTRlpName // 对象名称
*/
HANDLE threadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

DWORD WINAPI threadFuncA(LPVOID lpParamter)
{
	printf("threadFuncA 等待事件有信号!\n");
	WaitForSingleObject(threadEvent, INFINITE);
	printf("threadFuncA 等待事件信号成功,并把事件自动设置为无信号状态!\n");
	return 0;
}

DWORD WINAPI threadFuncB(LPVOID lpParamter)
{
	Sleep(5000);
	//给事件赋予信号
	SetEvent(threadEvent);
	printf("threadFuncB 给了事件信号!\n");
	return 0;
}

int main()
{
	HANDLE threadA = CreateThread(NULL, 0, threadFuncA, NULL, 0, NULL);
	HANDLE threadB = CreateThread(NULL, 0, threadFuncB, NULL, 0, NULL);
	
	WaitForSingleObject(threadA, INFINITE);
	CloseHandle(threadA);//CloseHandle只是关闭了系统句柄,该线程还是可以正常的运行
	CloseHandle(threadB);

	return 0;
}

执行结果:



5.临界区

CRITICAL_SECTION是最快的。其他内核锁(事件、互斥体),每进一次内核,都需要上千个CPU周期。
使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不会怎样。如果是主线程(GUI线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!临界区域的一个缺点就是:Critical Section不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical Section在MFC中的相应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::UnLock()离开临界区。

#include 
#include 
#include 


/*
使用临界区
*/
CCriticalSection cs;

DWORD WINAPI threadFuncA(LPVOID lpParamter)
{
	Sleep(2000);
	printf("**************threadFuncA 等待临界区解锁!\n");
	cs.Lock();
	printf("**************threadFuncA 等待临界区解锁成功,对其加锁!\n");
	printf("**************threadFuncA 把临界区解锁!\n");
	cs.Unlock();
	return 0;
}

DWORD WINAPI threadFuncB(LPVOID lpParamter)
{
	cs.Lock();
	printf("**************threadFuncB 把临界区锁住了!\n");
	Sleep(5000);
	printf("**************threadFuncB 把临界区解锁!\n");
	cs.Unlock();
	return 0;
}

int main()
{
	HANDLE threadA = CreateThread(NULL, 0, threadFuncA, NULL, 0, NULL);
	HANDLE threadB = CreateThread(NULL, 0, threadFuncB, NULL, 0, NULL);
	
	WaitForSingleObject(threadA, INFINITE);
	CloseHandle(threadA);//CloseHandle只是关闭了系统句柄,该线程还是可以正常的运行
	CloseHandle(threadB);

	return 0;
}

执行结果:C++ 多线程之间的通信方式(windows下实现)_第2张图片

 

你可能感兴趣的:(C++)