利用互斥对象实现线程同步的实例说明

来源:互联网 发布:cf自动准备源码 编辑:程序博客网 时间:2024/04/28 14:37

多线程编程中,如果我们稍有不注意,很容易出现一些意想不到的结果,主要原因就是多线程的同步处理;我们需要保证多个线程在共同运行时,进行对应资源的同步处理,保证一个线程访问共享资源时,其他线程不能访问该资源,这里将使用互斥对象实现线程同步;


在“认识多线程”这篇文章中有个实例2,该实例给出了两个线程共同完成g_Index的计数,并打印对应的线程标识;最终的运行结果存在不符合预期的地方;原因就是没有对g_Index这个共享资源进行保护;本节将利用互斥对象实现线程同步,解决这个问题;


互斥对象


互斥对象(Mutex)属于内核对象,它能确保线程拥有对单个资源的互斥范围权利,即线程A正在拥有资源Z,线程B恰好也要使用资源Z,则线程B会等到线程A使用完资源后,才去使用资源Z;

互斥对象包含一个使用数量,一个线程ID,一个计数器。其中ID用于标识哪个线程当前拥有这个互斥对象,计数器用于标识该线程拥有互斥对象的次数

这里会用到CreateMutex、ReleaseMutex、WaitForSingleObject这三个函数,利用这三个函数可以完成线程同步,下面介绍这几个函数;


CreateMutex


HANDLE WINAPI CreateMutex(  __in_opt  LPSECURITY_ATTRIBUTES lpMutexAttributes,  __in      BOOL bInitialOwner,  __in_opt  LPCTSTR lpName);
功能:

创建或者打开一个已经命名的或者匿名的互斥对象

参数:

lpMutexAttributes

一个指向LPSECURITY_ATTRIBUTES结构的指针,我们一般设置该值为NULL,表示让互斥对象使用默认的安全性;

bInitialOwner

指定互斥对象初始的拥有者,TRUE:创建者对象的线程获得该对象的使用权,内部计数器加1,否则,该线程不获得互斥对象的所有权;

lpName

指定互斥对象的名称,如果此参数为NULL,表示创建一个匿名的互斥对象;


ReleaseMutex


当我们对共享资源访问后,我们需要释放该对象的所有权,让这个互斥对象处于已通知状态;此时可以调用ReleaseMutex函数,调用该函数相当于互斥对象的计数器减1,其函数声明如下:

BOOL WINAPI ReleaseMutex(__in  HANDLE hMutex);

参数:

hMutex 需要释放的互斥对象的句柄

当前线程调用了该函数,则其他线程就有机会获得该对象的所有权,从而获得共享资源的访问;

WaitForSingleObject


线程若要获得互斥对象的所有权,则必须主动发出请求才能获得该互斥对象的所有权;获得对象的所有权,可以调用WaitForSingleObject函数来实现,若成功获得互斥对象的执行权,该互斥对象的计数器则增加1,其函数声明如下:

DWORD WINAPI WaitForSingleObject(  __in  HANDLE hHandle,  __in  DWORD dwMilliseconds);

参数:

hHandle

所请求互斥对象的句柄;一旦互斥对象处于有信号状态,该函数就返回。如果该互斥对象一致处于无状态对象,函数会一致等待,该线程暂停执行

dwMilliseconds

指定等待的时间间隔,以毫秒为单位。

如果该参数为0表示测试该对象的状态就立即返回,不关心线程是否获得所有权,相当于没有进行线程同步处理

如果该参数为INFINITE表示函数会一直等待,直到等待的对象处于有信号状态才会返回,这里能保证资源的独占性;


WaitForSingleObject函数返回的两种情况,其返回值有三个WAIT_OBJECT_0、WAIT_TIMEOUT、WAIT_ABANDONED:

1)指定的对象变成有信号状态

2)指定的等待时间间隔已过;


实例1


现在利用互斥对象实现符合线程安全的计数器打印,其具体代码如下:

#include "stdafx.h"#include <windows.h>#include <iostream>//#include <afxmt.h>using namespace std;int g_nIndex = 0;const int nMaxCnt = 50;//CCriticalSection csIndexLock;HANDLE hMetex = NULL;//新线程的起始地址DWORD WINAPI  Thread1Proc(LPVOID lpParameter){      while (TRUE)    {        //等待线程的执行权,无限期等待        //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行        WaitForSingleObject(hMetex,INFINITE);        if (g_nIndex++ < nMaxCnt)        {            cout << "Index = "<< g_nIndex << " ";            cout << "Thread1 is ruuning" << endl;        }        else        {            break;        }        //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体        ReleaseMutex(hMetex);    }    return 0;}//新线程的起始地址DWORD WINAPI  Thread2Proc(LPVOID lpParameter){      while (TRUE)    {        //等待线程的执行权,无限期等待        //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行,否则不执行        WaitForSingleObject(hMetex,INFINITE);        if (g_nIndex++ < nMaxCnt)        {            cout << "Index = " << g_nIndex << " ";            cout << "Thread2 is ruuning" << endl;        }        else        {            break;        }        //资源使用完,释放互斥对象,互斥对象的线程ID变为0,互斥对象处于有信号状体        ReleaseMutex(hMetex);    }    return 0;}int _tmain(int argc, _TCHAR* argv[]){HANDLE hThread1 = NULL;    HANDLE hThread2 = NULL;    //创建互斥对象,主线程没有执行权    hMetex = CreateMutex(NULL, FALSE, NULL);    //创建新的线程    hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);    hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);    //无须对新线程设置优先级等操作,关闭之    //良好的编码习惯    CloseHandle(hThread1);    CloseHandle(hThread2);    //主线程放弃执行全力    Sleep(5000);    return 0;}
运行结果:


实例2


如果在我们在创建互斥对象时,将CreateMetux的第二个对象设置为TURE,即

hMetex = CreateMutex(NULL, TRUE, NULL);
运行结果:



分析:

wMain主线程获得这个互斥对象的执行权,并且没有释放这个互斥对象,其他线程在WaitForSingleObject时,该互斥对象都属于未通知状态,因此Thread1和Thread2都不会执行;


实例3


在思路2的基础上,若我们在线程1和线程2的WaitForSingleObject函数前面,增加ReleaseMutex(hMutex)语句,执行情况和实例2一致;

第一点说明:

对互斥对象来说,它是唯一与线程相关的内核对象;当主线程拥有互斥对象时,操作系统将互斥对象的线程ID设置为主线程的ID

线程1在调用ReleaseMutex函数时,操作系统会判断当前线程的线程ID和互斥对象中的线程ID是否相等,只有相等才能正在释放;由于主线没有释放,互斥对象的线程ID是主线程的ID,导致其他线程没有执行的机会。对于互斥对象来说,必须是谁拥有谁完成释放


实例4


对于如下的主线程函数来说,线程1和线程2想获得执行权,需要释放两次,代码如下:

int _tmain(int argc, _TCHAR* argv[]){HANDLE hThread1 = NULL;    HANDLE hThread2 = NULL;    //创建互斥对象,主线程有执行权    hMetex = CreateMutex(NULL, TRUE, NULL);    WaitForSingleObject(hMetex,INFINITE);    //创建新的线程    hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);    hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);    //无须对新线程设置优先级等操作,关闭之    //良好的编码习惯    CloseHandle(hThread1);    CloseHandle(hThread2);    //CreateMutex创建时,主线程获得所有权,计数器+1    ReleaseMutex(hMetex);    //同一个线程内调用WaitForSingleObject,即此刻互斥对象处于未通知状态    //调用线程也会获得执行权,计数器再+1    ReleaseMutex(hMetex);    //主线程放弃执行全力    Sleep(5000);    return 0;}

第二点说明:

我们在调用WaitForSingleObject函数时,若请求的线程ID和互斥对象内部的线程ID是相同的,即使此时此互斥对象处于未通知状态,该线程还可以请求到该回车对象的所有权,计数器则会再加1;所以实例4中需要释放互斥对象两次才行;


实例5:


第三点说明:

当一个线程已经终止时(比如线程return),操作系统自动会帮助我们将互斥对象的线程ID设置为0,并且将引用计数器设置为0;

//新线程的起始地址DWORD WINAPI  Thread1Proc(LPVOID lpParameter){      //等待线程的执行权,无限期等待    //当该线程得到CPU时间片,并且互斥量处于通知状态,程序执行    WaitForSingleObject(hMetex,INFINITE);    cout << "Thread1 is ruuning" << endl;    //线程结束,自动释放互斥变量,使其处于已通知状态    return 0;}//新线程的起始地址DWORD WINAPI  Thread2Proc(LPVOID lpParameter){      WaitForSingleObject(hMetex,INFINITE);    cout << "Thread2 is ruuning" << endl;    //线程结束,自动释放互斥变量,使其处于已通知状态    return 0;}int _tmain(int argc, _TCHAR* argv[]){HANDLE hThread1 = NULL;    HANDLE hThread2 = NULL;    //创建互斥对象,主线程没有执行权    hMetex = CreateMutex(NULL, FALSE, NULL);    //创建新的线程    hThread1 = CreateThread(NULL,0,Thread1Proc,NULL,0,NULL);    hThread2 = CreateThread(NULL,0,Thread2Proc,NULL,0,NULL);    //无须对新线程设置优先级等操作,关闭之    //良好的编码习惯    CloseHandle(hThread1);    CloseHandle(hThread2);    //主线程放弃执行权力    Sleep(5000);    return 0;}

1 0
原创粉丝点击