线程同步三种方式

本文介绍多线程编程中常见的三种线程同步方式:信号量、事件对象和临界区,并对比了它们各自的优缺点。通过具体示例展示了如何避免多线程环境下因资源竞争而导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程同步的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种方式

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值