线程同步的3种方式 :
利用多线程编写应用程序的时候 我们经常遇到这样的问题:多个线程访问同一个资源
由于线程访问了该资源 就使得线程拥有了对资源的控制权 有时这样不加控制的访问会出现一切问题
如我们编写一个售票程序 有2个线程同时售票 :
程序源码如下:
#include <windows.h>
#include <iostream.h>
//子线程函数原型的声明 2个售票线程 fun1Proc 和fun2Proc
DWORD WINAPI fun1Proc(
LPVOID lpParameter // thread data
);
DWORD WINAPI fun2Proc(
LPVOID lpParameter // thread data
);
int ticket=100;//定义一个全局变量 表示剩余票数 便于线程访问
void main()
{
//一下创建线程 有关多线程程序的创建 可以详见MSDN CreateThread函数
HANDLE handle1;
HANDLE handle2;
handle1=CreateThread(NULL,0,fun1Proc,NULL,0,NULL);
handle2=CreateThread(NULL,0,fun2Proc,NULL,0,NULL);
CloseHandle(handle1);//关闭线程句柄
CloseHandle(handle2);
Sleep(4000);//我们让主线程暂停4秒 因为一旦主线程退出 其他子线程也将退出
}
DWORD WINAPI fun1Proc(
LPVOID lpParameter // thread data
){
while (true)//票没卖完就一直运行
{
if(ticket>0){
Sleep(1);//暂停1MS 为了便于观察出出错的结果
cout<<"This is fun1Proc sell "<< ticket --<<endl; //打印买票
}
else{
break;//退出
}
}
return 0;
}
//线程2同线程1
DWORD WINAPI fun2Proc(
LPVOID lpParameter // thread data
){
while (TRUE)
{
if(ticket>0){
Sleep(1);
cout<<"This is fun2Proc sell "<<ticket --<<endl;
}
else{
break;
}
}
return 0;
}
由于我们未对线程访问资源(此处为"票")进行控制 运行时 可能会出现卖出第0张票的结果 此处我们在线程中添加了Sleep(1)
使我们很容易观察到这种错误 如果没有此句 错误很难被发现
下面提供3中线程同步的方式:(源代码见文章最后)
1.信号量:
利用信号量进行同步 (注意信号量的计数器的值)
CreateMutex()函数 创建一个信号量
WaitForSingleObject()设置信号量为有信号 即信号量计数器+1
ReleaseMutex()释放信号量 信号量计数器-1
此外 我们还可用函数CloseHandle()函数来关闭信号量句柄
2.利用事件对象实现线程同步
CreateEvent() 创建事件对象
WaitForSingleObject()等待
SetEvent() 设置事件为有信号
ResetEvent()设置时间为无信号
此外 我们还可用函数CloseHandle()函数来关闭信号量句柄
区分人工重置的事件对象和自动重置的事件对象 即CreateEvent的第二个参数 TRUE为人工重置(一般不这样使用) FALSE
为自动重置
3.临界区
定义一个CRITICAL_SECTION类型变量 运用一下4个函数进行临界区的使用
InitializeCriticalSection 初始化临界区
DeleteCriticalSection 删除临界区
EnterCriticalSection 进入临界区
LeaveCriticalSection 离开临界区
,如果学习了操作系统 以上三个概念将会很容易理解 操作系统中有更为详细具体的解释
3中方式的优缺点
以下摘自孙鑫老师的课程PPT中:
1.互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对
象,可以在多个进程中的各个线程间进行同步。
2.关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键
代码段时无法设定超时值。
下面介绍一下如何让一个程序只有1个实例化运行 如我们QQ可以开多个 而有的程序只能开一个 比如卡巴斯基等程序
我们可以利用信号量和事件对象来实现 当创建一个信号量或者事件对象时 我们可以给其取一个名字 然后判断时候存在了此信号量
或者事件对象(用GetLastError获得错误然后和ERROR_ALREADY_EXISTS进行比较) 据此 我们可以让一个程序只有1个实例运行
所有程序源码见 我的资源:线程同步的3种方式