Windows多线程的同步与互斥

2024-05-09 18:18

本文主要是介绍Windows多线程的同步与互斥,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

来自:Windows多线程的同步与互斥

系统中的所有线程都必须拥有对各种系统资源的访问权,这些资源包括内存堆栈,串口,文件,窗口和许多其他资源。如果一个线程需要独占对资源的访问权,那么其他线程就无法完成它们的工作。反过来说,也不能让任何一个线程在任何时间都能访问所有的资源。如果在一个线程从内存块中读取数据时,另一个线程却想要将数据写入同一个内存块,那么这就像你在读一本书时另一个人却在修改书中的内容一样。这样,书中的内容就会被搞得乱七八糟,结果什么也看不清楚。

线程需要在下面两种情况下互相进行通信:
1.当有多个线程访问共享资源而不使资源被破坏时。
2.当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。

1)CriticalSection: 临界区 
适用范围: 单一进程的各线程之间用来排它性占有
特性: 不是内核对象,快速而有效。无法监测是否被线程放弃。如果在Critical Sections中间突然程序crash或是exit而没有调用LeaveCriticalSection,则结果是该线程所对应的内核不能被释放,该线程成为死线程。
函数: EnterCriticalSection LeaveCriticalSection
很好的封装:
[cpp]  view plain copy
  1. class CritSect  
  2. {  
  3. public:  
  4.     friend class Lock;  
  5.     CritSect() { InitializeCriticalSection(&_critSection); }  
  6.     ~CritSect() { DeleteCriticalSection(&_critSection); }  
  7. private:  
  8.     void Acquire(){ EnterCriticalSection(&_critSection); }  
  9.     void Release(){ LeaveCriticalSection(&_critSection); }  
  10.     CRITICAL_SECTION _critSection;  
  11. };  
  12. class Lock  
  13. {  
  14. public:  
  15.      Lock(CritSect& critSect):_critSect(critSect) { _critSect.Acquire(); }  
  16.      ~Lock(){ _critSect.Release(); }  
  17. private:  
  18.     CritSect& _critSect;  
  19. };  
调用:CritSect sect; Lock lock(sect);

2)Mutex: 互斥内核对象
适用范围: 不同线程之间用来排它性占有
特性: 核心对象,哪个线程拥有mutex,那么该mutex的ID和此线程的ID一样。
函数: CreateMutex ReleaseMutex

3)Event: 事件内核对象
适用范围: 用来控制对象信号的接收,常与信号系统结合起来
特性: 核心对象,有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时(signaled),等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。
Microsoft为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地调用wait函数等待到该对象时,自动重置的事件就会自动重置到未通知状态(nonsignaled)。通常没有必要为自动重置的事件调用ResetEvent()函数,因为系统会自动对事件进行重置。但是,Microsoft没有为人工重置的事件定义成功等待的副作用,所以需要调用ResetEvent()函数将Event设置为未通知状态(nonsignaled)。当调用SetEvent触发Auto-reset的Event条件时,如果没有被条件阻塞的线程,那么此函数仍然起作用,条件变量会处在触发状态(和Linux的pthread_cond_signal()不同)。
函数: CreateEvent OpenEvent PulseEvent SetEvent ResetEvent 

4)Semaphore: 信号内核对象 
适用范围: 用来限制资源占用
特性: 核心对象,没有拥有者,任何线程都可释放。信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
函数: CreateSemaphore OpenSemaphore ReleaseSemaphore

等待函数
1)WaitForSingleObject()
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止。
[cpp]  view plain copy
  1. DWORD dw = WaitForSingleObject(hProcess, 5000);  
  2. switch(dw)  
  3. {  
  4.    case WAIT_OBJECT_0:  
  5.       // The process terminated.  
  6.       break;  
  7.   
  8.    case WAIT_TIMEOUT:  
  9.       // The process did not terminate within 5000 milliseconds.  
  10.       break;  
  11.   
  12.    case WAIT_FAILED:  
  13.       // Bad call to function (invalid handle?)  
  14.       break;  
  15. }  

2)WaitForMultipleObjects()
WaitForMultipleObjects与WaitForSingleObject函数很相似,区别在于它允许调用线程同时查看若干个内核对象的已知状态。WaitForMultipleObjects函数的返回值告诉调用线程,为什么它会被重新调度。可能的返回值是WAIT_FAILED和WAIT_TIMEOUT,这两个值的作用是很清楚的。如果为fWaitAll参数传递TRUE,同时所有对象均变为已通知状态,那么返回值是WAIT_OBJECT_0。如果为fWaitAll传递FALSE,那么一旦任何一个对象变为已通知状态,该函数便返回。在这种情况下,你可能想要知道哪个对象变为已通知状态。返回值是WAIT_OBJECT_0与(WAIT_OBJECT_0+dwCount-1)之间的一个值。换句话说,如果返回值不是WAIT_TIMEOUT,也不是WAIT_FAILED,那么应该从返回值中减去WAIT_OBJECT_0。产生的数字是作为第二个参数传递给WaitForMultipleObjects的句柄数组中的索引。该索引说明哪个对象变为已通知状态。下面是说明这一情况的一些示例代码:
[cpp]  view plain copy
  1. HANDLE h[3];  
  2. h[0] = hProcess1;  
  3. h[1] = hProcess2;  
  4. h[2] = hProcess3;  
  5. DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);  
  6. switch(dw)   
  7. {  
  8.    case WAIT_FAILED:  
  9.       // Bad call to function (invalid handle?)  
  10.       break;  
  11.   
  12.    case WAIT_TIMEOUT:  
  13.       // None of the objects became signaled within 5000 milliseconds.  
  14.       break;  
  15.   
  16.    case WAIT_OBJECT_0 + 0:  
  17.       // The process identified by h[0] (hProcess1) terminated.  
  18.       break;  
  19.   
  20.    case WAIT_OBJECT_0 + 1:  
  21.       // The process identified by h[1] (hProcess2) terminated.  
  22.       break;  
  23.   
  24.    case WAIT_OBJECT_0 + 2:  
  25.       // The process identified by h[2] (hProcess3) terminated.  
  26.       break;  


3)SingleObjectAndWait()
DWORD SingleObjectAndWait(HANDLE hObjectToSignal,HANDLE hObjectToWaitOn,DWORD dwMilliseconds,BOOL fAlertable);
函数用于在单个原子方式的操作中发出关于内核对象的通知并等待另一个内核对象:hObjectToSignal参数必须标识一个互斥对象、信号对象或事件对象。hObjectToWaitOn参数用于标识下列任何一个内核对象:互斥对象、信标、事件、定时器、进程、线程、作业、控制台输入和修改通知。与平常一样,dwMilliseconds参数指明该函数为了等待该对象变为已通知状态,应该等待多长时间,而fAlertable标志则指明线程等待时该线程是否应该能够处理任何已经排队的异步过程调用。

4)MsgWaitForMultipleObjects(Ex)
MsgWaitForMultipleObjects和MsgWaitForMultipleObjectsEx这些函数与WaitForMultipleObjects函数十分相似。差别在于它们允许线程在内核对象变成已通知状态或窗口消息需要调度到调用线程创建的窗口中时被调度。创建窗口和执行与用户界面相关的任务的线程,应该调用MsgWaitForMultipleObjectsEx函数,而不应该调用WaitForMultipleObjects函数,因为后面这个函数将使线程的用户界面无法对用户作出响应。

Windows下的生产者消费者问题:推荐使用Semaphore版本,因为使用mutex版本时候,两个线程不能并行工作。

[cpp]  view plain copy
  1. #include "StdAfx.h"  
  2. #include <windows.h>  
  3. #include <stdio.h>  
  4.   
  5. #define BUFFER_SIZE 10  
  6.   
  7. typedef struct Prodcon  
  8. {  
  9.     int readpos;  
  10.     int writepos; //position for reading and writing  
  11.     int buffer[BUFFER_SIZE];  
  12. }Prodcon;  
  13.   
  14. bool isOver = false;  
  15.   
  16. HANDLE hmutex;   
  17. HANDLE hfullsemaphore;   
  18. HANDLE hemptysemaphore;   
  19.   
  20. HANDLE notfullevent;  
  21. HANDLE notemptyevent;  
  22.   
  23. void init(Prodcon * pb)  
  24. {  
  25.     pb->readpos = 0;  
  26.     pb->writepos = 0;  
  27. }  
  28.   
  29. //store an integer in the buffer  
  30. void put(Prodcon* pb,int data)  
  31. {  
  32.     WaitForSingleObject(hemptysemaphore,INFINITE);  
  33.     pb->buffer[pb->writepos] = data;  
  34.     pb->writepos++;  
  35.     pb->writepos %= BUFFER_SIZE;  
  36.     ReleaseSemaphore(hfullsemaphore,1,NULL);  
  37. }  
  38.   
  39. //read an integer from the buffer  
  40. int get(Prodcon* pb)  
  41. {  
  42.     int data;  
  43.     WaitForSingleObject(hfullsemaphore,INFINITE);  
  44.     data = pb->buffer[pb->readpos];  
  45.     pb->readpos ++;  
  46.     pb->readpos %= BUFFER_SIZE;  
  47.     ReleaseSemaphore(hemptysemaphore,1,NULL);                         
  48.     return data;  
  49. }  
  50.   
  51. //put data use event trigger  
  52. void put_event(Prodcon* pb,int data)  
  53. {  
  54.     WaitForSingleObject(hmutex,INFINITE);  
  55.     //wait until buffer is not full  
  56.     while((pb->writepos+1)%BUFFER_SIZE == pb->readpos)  
  57.     {  
  58.         printf("wait for not full\n");  
  59.         ReleaseMutex(hmutex);   
  60.         WaitForSingleObject(notfullevent,INFINITE);  
  61.     }       
  62.     pb->buffer[pb->writepos] = data;  
  63.     pb->writepos ++;  
  64.     pb->writepos %= BUFFER_SIZE;  
  65.     SetEvent(notemptyevent);  
  66.     ReleaseMutex(hmutex);      
  67. }  
  68.   
  69. //get data use event trigger  
  70. int get_event(Prodcon* pb)  
  71. {  
  72.     int data;  
  73.     WaitForSingleObject(hmutex,INFINITE);  
  74.     //wait until buffer is not empty  
  75.     while(pb->writepos == pb->readpos)  
  76.     {  
  77.   
  78.         printf("wait for not empty\n");  
  79.         ReleaseMutex(hmutex);    
  80.         WaitForSingleObject(notemptyevent,INFINITE);  
  81.     }  
  82.     data = pb->buffer[pb->readpos];  
  83.     pb->readpos ++;  
  84.     pb->readpos %= BUFFER_SIZE;  
  85.     SetEvent(notfullevent);  
  86.     ReleaseMutex(hmutex);  
  87.     return data;  
  88. }  
  89.   
  90. DWORD WINAPI produce(LPVOID lppara)  
  91. {  
  92.     Prodcon* pb = (Prodcon*)lppara;  
  93.     while(1)  
  94.     {  
  95.         for(int i=1; i<=50; ++i)  
  96.         {  
  97.             //put(pb,i);  
  98.             //test event trigger function  
  99.             put_event(pb,i);  
  100.             printf("put a data: %d\n",i);  
  101.             Sleep(10); //producer is fast  
  102.         }  
  103.         isOver = true;  
  104.         break;  
  105.     }  
  106.     return NULL;  
  107. }  
  108.   
  109. DWORD WINAPI consume(LPVOID lppara)  
  110. {  
  111.     Prodcon* pb = (Prodcon*)lppara;  
  112.     while(1)  
  113.     {  
  114.         //int d = get(pb);  
  115.         //test event trigger function  
  116.         int d = get_event(pb);  
  117.         printf("get data: %d\n",d);  
  118.         if(isOver == true && pb->readpos == pb->writepos)  
  119.         {  
  120.             printf("OVER!\n");  
  121.             break;  
  122.         }  
  123.         Sleep(100); //consumer is slow  
  124.     }  
  125.     return NULL;  
  126. }  
  127.   
  128. int main()  
  129. {  
  130.     hmutex = CreateMutex(NULL,false,NULL);  
  131.     DWORD readerdata;  
  132.     DWORD writerdata;  
  133.     Prodcon pb;  
  134.   
  135.     init(&pb);  
  136.   
  137.     //test produce/consume semaphore trigger  
  138.     /*hfullsemaphore = CreateSemaphore(NULL,0,BUFFER_SIZE,NULL); 
  139.     hemptysemaphore = CreateSemaphore(NULL,BUFFER_SIZE,BUFFER_SIZE,NULL); 
  140.     if(CreateThread(NULL,0,produce,&pb,0,&writerdata)==NULL) 
  141.         return -1; 
  142.     if(CreateThread(NULL,0,consume,&pb,0,&readerdata)==NULL) 
  143.         return -1;*/  
  144.   
  145.     //test produce/consume Event trigger  
  146.     notfullevent = CreateEvent(NULL,FALSE,FALSE,NULL); //unnamed auto-reset event  
  147.     notemptyevent = CreateEvent(NULL,FALSE,FALSE,NULL); //unnamed auto-reset event  
  148.     if(CreateThread(NULL,0,produce,&pb,0,&writerdata)==NULL)  
  149.         return -1;  
  150.     if(CreateThread(NULL,0,consume,&pb,0,&readerdata)==NULL)  
  151.         return -1;  
  152.   
  153.     char ch;  
  154.     while(1)  
  155.     {  
  156.         ch = getchar(); //press "e" to exit  
  157.         if(ch == 'e'break;  
  158.     }  
  159.   
  160.     printf("Program ends successfully\n");  
  161.     CloseHandle(hmutex);  
  162.     CloseHandle(hfullsemaphore);  
  163.     CloseHandle(hemptysemaphore);  
  164.   
  165.     return 0;  
  166. }


参考资料: 

http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx
http://www.cppblog.com/mzty/archive/2008/07/29/57470.html

这篇关于Windows多线程的同步与互斥的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/974137

相关文章

java多线程—java线程的创建和线程的生命周期

<span style="font-size:14px;">package com.dufy.thread;/***1 线程学习* @author aflyun * */public class TestCreateThread {public static void main(String[] args) {testThread tt = new testThread();testRunab

java多线程—Thread.Join()和Thread.Sleep()

注: join方法的功能就是使异步执行的线程变成同步执行。也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用join方法。如果不使用join方法,就不能保证当执行到start方法后面的某条语句时,这个线程一定会执行完。而使用join方法后,直到这个线程退出,程序才会往下执行。 这两天一直在研究Thr

关于低版本Windows系统在SpringBoot项目中无法运行OpenCV的问题

文章目录 1 摘要2 异常信息3 解决OpenCV依赖问题3.1 Windows 7 安装 OpenCV 依赖3.2 Windows Server 2012 安装 OpenCV 依赖 4 推荐参考资料 1 摘要 曾经的典操作系统 Windows 7 ,在如今的 2024 年依旧占有一定的市场份额。在 SpringBoot 集成 OpenCV 的过程中也提到过操作系统版本过低会导

VMware虚拟机-安装程序无法自动安装virtual machine......_windows server 2008 R2

系统版本:windows server 2008 R2 问题-安装程序无法自动安装virtual machine… 在使用虚拟机安装windows server 2008 R2系统中,安装VMware Tools工具安祖啊寄给你失败,提示安装程序无法自动安装virtual machine…,必须手动安装 原因 官网的说明:https://knowledge.broadcom.co

python 脚本压缩文件linux 正常,windows 文件夹/文件名称 被加上了上级文件夹名

场景: php 在调用python 脚本,进行文件压缩(因为php的压缩大文件总是超时),linux/mac 环境文件/文件夹名压缩前后一致,windows 压缩后 文件/文件夹名被改变为 上级 文件夹+原名 原因: windows 和 mac、linux 文件路径的分隔符 不一样 解决: 使用php 自带的分隔符常量DIRECTORY_SEPARATOR,该常量会根据 不同系统,变化

Windows上通过xshell连接服务器(通过sftp传输文件)

一: (1)打开xshell如下图: (2)点击文件选择新建会话 (3)点击用户身份验证,输入用户名和密码,然后点击连接就ok啦 二: (1)下载xftp直接安装,在xshell上点击下图红色图标

【Linux】线程周边001之多线程

👀樊梓慕:个人主页  🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》 🌝每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.线程的理解  2.地址空间与页表 3.线程控制 3.1POSIX线程库 3.2创建线程 pthread_create 3.3获取线程ID pthread_self

如何同步管理1000个设备的VLAN数据?

什么是VLAN? VLAN,也就是虚拟局域网,是通过为子网提供数据链路连接来抽象出局域网的概念。在企业网中,一个企业级交换机一般是24口或者是48口,连接这些接口的终端在物理上形成一个广播域。广播域过大,就会导致大量的广播报文泛滥,减少了可利用的网络带宽,同时也减少了广播域的安全性。 这个时候,就可以在交换机上使用VLAN技术,划分出多个逻辑上的广播域。 各个广播域之间二层隔离,

java并发实战第六章(2)非阻塞式线程安全列表与一般List集合多线程情况下的比较

这里我把ConcurrentLinkedDeque与List进行对比测试了一下,发现在多线程情况下一般的集合会出现很大的并发性问题,下面就一起探索一下 1.使用ConcurrentLinkedDeque实现的多线程读写数据 任务:添加大量的数据到一个列表集合中 从同一个列表中移除大量的数据 /*** * @author fcs* @date 2015-6-21* 描述:向集合中添加元素,添