c++实现进程与线程的同步互斥
有四种⽅法可以实⾏进程互斥,这 4 种⽅法具体定义如下 在《操作系统教程》ISBN 7-5053-6193-7 ⼀书中可以到更加 详细的解释
1 临界区:通过对多线程的串⾏化来访问公共资源或⼀段代码,速度快,适合控制数据访 问。
2 互斥量:为协调共同对⼀个共享资源的单独访问⽽设计的。
3 信号量:为控制⼀个具有有限数量⽤户资源⽽设计。
4 事 件:⽤来通知线程有⼀些事件已发⽣,从⽽启动后继任务的开始。
临界区( Section): 临界区(Critical Section) 保证在某⼀时刻只有⼀个线程能访问数据的简便办法。 在任意时刻只允许⼀ 个线程对共享资源进⾏访问。如果有多个线程试图同时访问临界区,那么在有⼀ 个线程进⼊后其他所有试图访问此临界区的线程将被挂起, 并⼀直持续到进⼊临 界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到⽤原 ⼦⽅式操作共享资源的⽬的。
临界区包含两个操作原语: EnterCriticalSection() 进⼊临界区 LeaveCriticalSection() 离开临界区 EnterCriticalSection ) (
语句执⾏后代码将进⼊临界区以后⽆论发⽣什么, 必须确保与之匹配的 LeaveCriticalSection()都能够被执⾏到。否则临界区 保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能⽤来同步本进程内的线程,⽽不可⽤来同步多个进程中的线程。
MFC 提供了很多功能完备的类,我⽤ MFC 实现了临界区。MFC 为临界区提供 有⼀个 CCriticalSection 类,使⽤该类进⾏线程同步处理是⾮常简单的。只需 在线程函数中⽤ CCriticalSection 类成员函数 Lock()和 UnLock()标定出被 保护代码⽚段即可。Lock()后代码⽤到的资源⾃动被视为临界区内的资源被保 护。UnLock 后别的线程才能访问这些资源。
//CriticalSection
CCriticalSection global_CriticalSection; // 共享资源
char global_Array[256]; //初始化共享资源
void InitializeArray()
{ for(int i = 0;i<256;i++)
{ global_Array[i]=I; } } //写线程
UINT Global_ThreadWrite(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); //进⼊临界区
global_CriticalSection.Lock();
for(int i = 0;i<256;i++)
{ global_Array[i]=W; ptr->SetWindowText(global_Array); Sleep(10); } //离开临界区
global_CriticalSection.Unlock(); return 0; } //删除线程
UINT Global_ThreadDelete(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); //进⼊临界区
global_CriticalSection.Lock();
for(int i = 0;i<256;i++) { global_Array[i]=D; ptr->SetWindowText(global_Array); Sleep(10); } //离开临界区
global_CriticalSection.Unlock(); return 0; } //创建线程并启动线程
void CCriticalSectionsDlg::OnBnClickedButtonLock() { //Start the first Thread CWinThread *ptrWrite =
AfxBeginThread(Global_ThreadWrite, &m_Write, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); ptrWrite-
>ResumeThread(); //Start the second Thread CWinThread *ptrDelete = AfxBeginThread(Global_ThreadDelete, &m_Delete, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); ptrDelete->ResumeThread(); }
在测试程序中,Lock UnLock 两个按钮分别实现,在有临界区保护共享资源 的执⾏状态,和没有临界区保护共享资源的执⾏状态。
2互斥量(Mutex): 互斥量(Mutex) 互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限, 由于互斥对象只有⼀个, 因此就决定了任何情况下此共享资源都不会同时被多个 线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以 便其他线程在获得后得以访问资源。互斥量⽐临界区复杂。因为使⽤互斥不仅仅 能够在同⼀应⽤程序不同线程 中实现资 源的安全共享, ⽽且可以在不同应⽤程序 的线程之间实现对资源的安全共享。
互斥量包含的⼏个操作原语: CreateMutex() 创建⼀个互斥量 OpenMutex() 打开⼀个互斥量 ReleaseMutex() 释放互斥量WaitForMultipleObjects() 等待互斥量对象
同样 MFC 为互斥量提供有⼀个 CMutex 类。 使⽤ CMutex 类实现互斥量操作⾮ 常简单,但是要特别注意对 CMutex 的构造函数的调⽤CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL) 不⽤的参数不能乱填,乱填会出现⼀些意想不到的运⾏结果。 //创建互斥量 CMutex global_Mutex(0,0,0); // 共享资源 char
global_Array[256]; void InitializeArray() { for(int i = 0;i<256;i++) { global_Array[i]=I; } } UINT Global_ThreadWrite(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); global_Mutex.Lock(); for(int i = 0;i<256;i++) {
global_Array[i]=W; ptr->SetWindowText(global_Array); Sleep(10); } global_Mutex.Unlock(); return 0; } UINT
Global_ThreadDelete(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); global_Mutex.Lock(); for(int i = 0;i<256;i++) { global_Array[i]=D; ptr->SetWindowText(global_Array); Sleep(10); } global_Mutex.Unlock(); return 0; } 同样在测试程序中,Lock UnLock 两个按钮分别实现,在有互斥量保护共享 资源的执⾏状态,和没有互斥量保护共享资源的执⾏状态。
3信号量(Semaphores): 信号量(Semaphores) 信号量对象对线程的同步⽅式与前⾯⼏种⽅法不同, 信号允许多个线程同时 使⽤共享资源,这与操作系统中的 PV 操作相同。它指出了同时访问共享资源的 线程最⼤数⽬。它允许多个线程在同⼀时刻访问同⼀资源,但是需要限制在同⼀ 时刻访问此资源的最⼤线程数⽬。在⽤ CreateSemaphore()创建信号量时即要 同时指出允许的最⼤资源计数和当前可⽤资源计数。 ⼀般是将当前可⽤资源计数 设置为最⼤资源计数,每增加⼀个线程对共享资源的访问,当前可⽤资源计数就 会减 1,只要
当前可⽤资源计数是⼤于 0 的,就可以发出信号量信号。但是当前 可⽤计数减⼩到 0 时则说明当前占⽤资源的线程数已经达到了所允许的最⼤数 ⽬,不能在允许其他线程的进⼊,此时的信号量信号将⽆法发出。线程在处理完 共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可⽤资源 计数加 1。在任何时候当前可⽤资源计数决不可能⼤于最⼤资源计数。 PV 操作及信号量的概念都是由荷兰科学家 E.W.Dijkstra 提出的。信号量 S 是⼀个整数,S ⼤于等于零时代表可供并发进程使⽤的资源实体数,但 S ⼩于零时则表⽰正在等待使⽤共享资源的进程数。 P 操作申请资源: (1)S 减 1; (2)若 S 减 1 后仍⼤于等于零,则进程继续执⾏; (3)S 减 1 后⼩于零, 若 则该进程被阻塞后进⼊与该信号相对应的队列中, 然后转⼊进程调度。 V 操作 释放资源: (1)S 加 1; (2)若相加结果⼤于零,则进程继续执⾏; (3) 若相加结果⼩于等于零, 则从该信号的等待队列中唤醒⼀个等待进程, 然后再返回原进程继续执⾏或转⼊进程调度。 信号量包含的⼏个操作原语: CreateSema
phore() 创建⼀个信号量 OpenSemaphore() 打开⼀个信号量ReleaseSemaphore() 释放信号量 WaitForSingleObject() 等待信号量 //信号量句柄 HANDLE global_Semephore; // 共享资源char global_Array[256]; void InitializeArray() { for(int i = 0;i<256;i++) { global_Array[i]=I; } } //线程 1 UINT
Global_ThreadOne(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); //等待对共享资源请求被通过 等于 P 操作 WaitForSingleObject(global_Semephore, INFINITE); for(int i = 0;i<256;i++) { global_Array[i]=O; ptr-
>SetWindowText(global_Array); Sleep(10); } //释放共享资源 等于 V 操作 ReleaseSemaphore(global_Semephore, 1, NULL); return 0; } UINT Global_ThreadTwo(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); WaitForSingleObject(global_Semephore, INFINITE); for(int i = 0;i<256;i++) { global_Array[i]=T; ptr-
>SetWindowText(global_Array); Sleep(10); } ReleaseSemaphore(global_Semephore, 1, NULL); return 0; } UINT
Global_ThreadThree(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText("");
WaitForSingleObject(global_Semephore, INFINITE); for(int i = 0;i<256;i++) { global_Array[i]=H; ptr-
>SetWindowText(global_Array); Sleep(10); } ReleaseSemaphore(global_Semephore, 1, NULL); return 0; } void CSemaphoreDlg::OnBnClickedButtonOne() { //设置信号量 1 个资源 1 同时只可以有⼀个线程访问 global_Semephore= CreateSemaphore(NULL, 1, 1, NULL); this->StartThread(); // TODO: Add your control notification handler code here } void CSemaphoreDlg::OnBnClickedButtonTwo() { //设置信号量 2 个资源 2 同时只可以有两个线程访问 global_Semephore= CreateSemaphore(NULL, 2, 2, NULL); this->StartThread(); // TODO: Add your control notification handler code here } void CSemaphoreDlg::OnBnClickedButtonThree() { //设置信号量 3 个资源 3 同时只可以有三个线程访问 global_Semephore= CreateSemaphore(NULL, 3, 3, NULL); this->StartThread(); // TODO: Add your control notification handler code here } 信号量的使⽤特点使其更适⽤于对 Socket(套接字)程序中线程的同步。 例如,⽹络上的 HTTP 服务器要对同⼀时间内访问同⼀页⾯的⽤户数加以限制, ⽽页⾯则是待保护的 这时可以为每⼀个⽤户对服务器的页⾯请求设置⼀个线程, 共享资源, 通过使⽤信号量对线程的同步作⽤可以确保在任⼀时刻⽆论有多少⽤ 户对某⼀页⾯进⾏访问,只有不⼤于设定的最⼤⽤户数⽬的线程能够进⾏访问, ⽽其他的访问企图则被挂起, 只有在有⽤户退出对此页⾯的访问后才有可能进⼊。
4 事件(Event): 事件(Event)事件对象也可以通过通知操作的⽅式来保持线程的同步。 并且可以实现不同 进程中的线程同步操作。 信号量包含的⼏个操作原语:
CreateEvent() 创建⼀个信号量
一个线程可以包含多个进程OpenEvent() 打开⼀个事件
SetEvent() 回置事件
WaitForSingleObject() 等待⼀个事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects( IN DWORD nCount, // 等待句柄数 IN CONST HANDLE *lpHandles, //指向句柄数组 IN BOOL bWaitAll, //是否完全等待标志 IN DWORD dwMilliseconds //等待时间 ) 参数 nCount 指定了要等待的内核对象的数⽬,存放这些内核对象的数组由 lpHandles 来指向。fWaitAll 对指定的这 nCount 个内核对象的两种等待⽅式进 ⾏了指定,为 TRUE 时当所有对象都被通知时函数才会返回,为 FALSE 则只要其 中任何⼀个得到通知就可以返回。dwMilliseconds 在这⾥的作⽤与在
WaitForSingleObject()中的作⽤是完全⼀致的。如果等待超时,函数将返回 WAIT_TIMEOUT。 //事件数组 HANDLE
global_Events[2]; // 共享资源 char global_Array[256]; void InitializeArray() { for(int i = 0;i<256;i++) { global_Array[i]=I; } } UINT Global_ThreadOne(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); for(int i = 0;i<256;i++) { global_Array[i]=O; ptr->SetWindowText(global_Array); Sleep(10); } //回置事件 SetEvent(global_Events[0]); return 0; } UINT Global_ThreadTwo(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); for(int i = 0;i<256;i++) {
global_Array[i]=T; ptr->SetWindowText(global_Array); Sleep(10); } //回置事件 SetEvent(global_Events[1]); return 0; } UINT Global_ThreadThree(LPVOID pParam) { CEdit *ptr=(CEdit *)pParam; ptr->SetWindowText(""); //等待两个事件都被回置WaitForMultipleObjects(2, global_Events, true, INFINITE); for(int i = 0;i<256;i++) { global_Array[i]=H; ptr-
>SetWindowText(global_Array); Sleep(10); } return 0; } void CEventDlg::OnBnClickedButtonStart() { for (int i = 0; i < 2; i++) { //实例化事件 global_Events[i]=CreateEvent(NULL,false,false,NULL); } CWinThread *ptrOne =
AfxBeginThread(Global_ThreadOne, &m_One, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); ptrOne-
>ResumeThread(); //Start the second Thread CWinThread *ptrTwo = AfxBeginThread(Global_Threa
dTwo, &m_Two,
THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); ptrTwo->ResumeThread(); //Start the Third Thread CWinThread
*ptrThree = AfxBeginThread(Global_ThreadThree, &m_Three, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
ptrThree->ResumeThread(); // TODO: Add your control notification handler code here } 事件可以实现不同进程中的线程同步操作, 并且可以⽅便的实现多个线程的 优先⽐较等待操作,例如写多个 WaitForSingleObject 来代替 WaitForMultipleObjects 从⽽使编程更加灵活。
总结: 总结: 1. 互斥量与临界区的作⽤⾮常相似,但互斥量是可以命名的,也就是说它 可以跨越进程使⽤。所以创建互斥量需要的资源更多,所以如果只为了在进程内 部是⽤的话使⽤临界区会带来速度上的优势并能够减少资源占⽤量。 因为互斥量 是跨 进程的互斥量 ⼀旦被创建,就可以通过名字打开它。 2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨 越进程使⽤来进⾏同步数据操作,⽽其他的对象与数据同步操作⽆关,但对于进 程和线程来讲,如果进程和线程在运⾏状态则为⽆信号状态,在退出后为有信号 状态。所以可以使⽤ WaitForSingleObject 来等待进程和线程退出。 3. 通过互斥量可以指定资源被独占的⽅式使⽤,但如果有下⾯⼀种情况通 过互斥量就⽆
法处理, ⽐如现在⼀位⽤户购买了⼀份三个并发访问许可的数据库 系统,可以根据⽤户购买的访问许可数量来决定有多少个线程/进程能同时进⾏ 数据库操作,这时候如果利⽤互斥量就没有办法完成这个要求,信号灯对象可以 说是⼀种资源计数器。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论