多线程(三)多线程同步_基本介绍及mutex互斥体

同步进制的引入为了解决以下三个主要问题:
1.控制多个线程之间对共享资源访问,保证共享资源的完整性
例如:线程A对共享资源进行写入,线程B读取共享资源
2.确保多个线程之间的动作以指定的次序发生
例如:线程B以线程A结束为条件进行触发运行
3.控制共享资源的最大访问数量
例如:有10个线程需要访问共享资源,同时只允许5个线程访问,那剩余线程就需要放入队列中进行等待

同步对象分类:
用户模式下的同步对象:例如关键段等
优点:速度快
缺点:不能跨进程,容易引起死锁
内核模式下的同步对象:例如互斥量,信号量,事件等
优点:跨进程,安全性高
缺点:速度慢(需要从当前用户模式下切换到内核模式,需要消耗时间)

等待函数(同步函数)
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
1.hHandle 对象句柄,如Event(事件)、Mutex(互斥锁)、Semaphore(信号)、Thread(线程)、process(进程)等等内核对象句柄
2.dwMilliseconds 等待时间间隔,毫秒
如果dwMilliseconds为非零值,函数会处于等待状态直到hHandle标志的对象成为触发状态,或者时间超时,函数才会返回
如果dwMilliseconds为0值,不论hHandle标志的对象是否是触发状态,都会立即返回
如果dwMilliseconds为INFINITE,函数会无限等待直到hHandle标志的对象成为触发状态后才会返回
返回值:
WAIT_OBJECT_0 对象是触发状态
WAIT_TIMEOUT 等待超时,等待时间内对象一直处于未触发状态
WAIT_ABANDONED 拥有mutex的线程在结束时没有释放对象
WAIT_FAILED  传递了一个无效句柄,出现错误,可通过GetLastError得到错误代码

注意:
1.调用等待函数时,会挂起当前代码所处的线程,直到函数返回时线程才会恢复执行.避免在窗口线程中调用此函数,因为一旦调用,窗口线程被挂起,函数没有返回前整个窗口都是处于假死状态。
2.当第一个参数是一个mutex对象时,有以下三种情况:
一.如果mutex对象存在拥有者并且不是当前线程,就会根据第二个参数进行等待.根据返回值如果为WAIT_OBJECT_0代表当前线程获得对象拥有权。如果为WAIT_TIMEOUT代表超时未获得对象拥有权。如果为WAIT_ABANDONED代表拥有mutex的线程在结束时没有释放对象,此时无法获知通过mutex所保护的共享资源是否被破坏.
二.如果mutex对象不存在拥有者,当前线程将会自动成为新的拥有者,返回WAIT_OBJECT_0
三.如果mutex对象拥有者是当前线程,也是直接返回WAIT_OBJECT_0
每次调用WaitForSingleObject,如果返回值为WAIT_OBJECT_0,mutex对象内部的递归计数器会+1,后面一定要使用ReleaseMutex进行释放使递归计数器-1.

演示:检测某个进程是否结束

 1     //不进行等待,只获取进程是否有信号
 2     //注意:进程启动后是无信号状态,只有当点击关闭按钮进程退出时才会变为有信号状态
 3     DWORD dwCode = WaitForSingleObject(hProcess,0);
 4     switch (dwCode)
 5     {
 6     case WAIT_OBJECT_0: //有信号状态,表示进程己退出
 7         printf("Process is Exit!
");
 8         break;
 9     case WAIT_TIMEOUT: //无信号状态,表示进程还在运行中
10         printf("Process is Running
");
11         break;
12     case WAIT_FAILED: //错误
13         {
14             DWORD dwError = GetLastError();
15             printf("Fail:%d
",dwError);
16         }
17     default:
18         break;
19     }
20     CloseHandle(hProcess);
21 
22     getchar();
23     return 0;
演示1

mutex互斥对象:
它是系统内核中的一种数据结构,用于确保一个线程独占一个资源的访问.
包含三个部分:
使用计数器    统计有多少个线程在调用用该对象
线程ID   当前占用这个互斥对象的线程ID.线程ID为0,代表没有被任何线程占用,处于触发状态.反之线程ID不为0,代表被一个线程占用,处于未触发状态
递归计数器   统计当前占用这个互斥对象的线程占用了该互斥对象的次数

创建互斥体对象
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName)
1.lpMutexAttributes 指向安全属性指针,通常为NULL
2.bInitialOwner 初始化互斥对象所有者,通常为FASLE,也就是没有所有者
3.lpName 指定互斥体对象的名字
返回值:
成功返回新互斥体对象句柄,失败返回0
注意:如果己经有同名的互斥体对象存在,函数将返回己有的互体斥对象句柄,不会再创建新的互斥体对象

释放互斥体对象
BOOL ReleaseMutex(HANDLE hMutex)
1.hMutex 互斥体对象句柄
返回值:
成功返回真,失败返回假

当线程中调用ReleaseMutex时,该函数会判断当前线程ID与互斥体对象的线程ID是否相同?如果相同,互斥体对象的递归计数器会-1,如果不相同不进行任何操作,直接返回FASLE,此时调用GetLastErro,将返回ERROR_NOT_OWNER(试图释放不是调用者拥有的互斥对象)
每调用一次该函数会使对象的占用计数器-1.如果线程调用等待函数返回WAIT_OBJECT_0不止一次,那ReleaseMutex就得调用相同次数,才能使占用计数器变为0.当占用计数器变为0时,系统内部会把该互斥体对象的线程ID设为0,也就是失去拥有者,此时该互斥对象成为可触发状态.
注意:如果一个线程是互斥体对象的拥有者,但是没有释放互斥对象就结束了线程,此后系统在检则互斥体对象的线程ID时发现此线程己结束,系统将自动把互斥对象的ID复置为0,并将它的递归计数器复置为0.然后从等待线程中寻找一个线程做为它的新的拥有者,并将递归计数器设置为1,同时这个等待线程变为可调度状态

使用互斥体对象配合等待函数实现同步的流程:

假设有一个全局的共享资源,同时有多个线程进行读写。为了保证共享资源的完整性,同一时刻只允许一个线程读写操作

1.创建一个互斥体对象,第二个参数为0,代表该互斥体对象没有所有者,保存返回的对象句柄供多个线程访问
2.多个线程内部在访问共享资源前,先调用WaitForSingleObject传入保存的互斥体对象句柄,然后再访问共享资源,最后再ReleaseMutex.

这样当有一个线程A执行WaitForSingleObject时,会获得互斥体对象所有权,此时互斥体对象的线程ID将被修改为线程A的ID,然后递归计数器为1,使用计数器为1,然后线程A独占访问共享资源。在没有调用ReleaseMutex前,后面调用WaitForSingleObject的线程由于对象是未触发状态,最终都将进入等待状态.直到线程A调用ReleaseMutex使递归计数器-1后变为0,此时系统会把线程ID设为0,交出所有权,该对象又成为触发状态,下一个线程才能获得所有权,独占访问共享资源。

编写一个Demo用于演示mutex互斥体基本操作

功能介绍:

模拟售票系统,有三个售票窗口(三个线程),每隔1秒售出1张票,售票前提示剩余总票数,以及提示售出1张票。当剩余总票售为0时,提示售票窗口关闭(线程结束)

开始编写代码:

1. 创建个基于对话框的工程MutexDemo

2. 添加一个编辑框用于显示售票信息,修改ID为IDC_EDIT_SHOWINFO, 修改属性为不可读.

3.添加一个按钮用于启动线程,修改ID为IDC_BTN_SELLTICKET

4. 先定义相关全局变量和线程函数前置声明

1 int g_nTickNum = 10; //总票数
2 CEdit* g_editShowInfo; //编辑框控件指针
3 HANDLE g_hMutex; //互斥体对象句柄
4 
5 //线程函数前置声明
6 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam);
7 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam);
8 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam);
全局变量和线程函数前置声明

5.OnInitDialog中添加相应代码

 1 BOOL CMutexDemoDlg::OnInitDialog()
 2 {
 3     CDialogEx::OnInitDialog();
 4 
 5     // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
 6     //  执行此操作
 7     SetIcon(m_hIcon, TRUE);            // 设置大图标
 8     SetIcon(m_hIcon, FALSE);        // 设置小图标
 9 
10     //创建mutex互斥体对象
11     g_hMutex = CreateMutex(NULL,FALSE,0);
12     //获取EDIT控件指针,供线程内部访问
13     g_editShowInfo = (CEdit*)GetDlgItem(IDC_EDIT_SHOWINFO);
14 
15 
16     return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
17 }
OnInitDialog

6.按钮_启动线程事件

1 //按钮_开始售票
2 void CMutexDemoDlg::OnBnClickedBtnSellticket()
3 {
4     //创建三个售票窗口线程,创建后直接关闭句柄
5     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_One,NULL,0,NULL));
6     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Two,NULL,0,NULL));
7     CloseHandle(CreateThread(NULL,0,ThreadSellTicket_Three,NULL,0,NULL));
8 }
按钮_启动线程事件

7.三个售票窗口线程代码

 1 DWORD WINAPI ThreadSellTicket_One(LPVOID lpParam)
 2 {
 3     CString strEdit;
 4     CString strNew;
 5     while (true)
 6     {
 7         Sleep(1000);
 8         WaitForSingleObject(g_hMutex,INFINITE);
 9         g_editShowInfo->GetWindowText(strEdit);
10         if(g_nTickNum > 0)
11         {
12             strNew.Format(_T("线程1:剩余票数:%d,售出1张票
"),g_nTickNum--);        
13             strEdit += strNew;
14             if(g_nTickNum == 0)
15             {
16                 strEdit += _T("线程1:票己售空,关闭售票窗口
");
17                 g_editShowInfo->SetWindowText(strEdit);
18                 ReleaseMutex(g_hMutex);
19                 break;
20             }
21             else
22             {
23                 g_editShowInfo->SetWindowText(strEdit);
24                 ReleaseMutex(g_hMutex);
25             }
26         }
27         else
28         {
29             strEdit += _T("线程1:票己售空,关闭售票窗口
");
30             g_editShowInfo->SetWindowTextW(strEdit);
31             ReleaseMutex(g_hMutex);
32             break;
33         }
34     }
35     return true;
36 }
线程_售票窗口1
 1 DWORD WINAPI ThreadSellTicket_Two(LPVOID lpParam)
 2 {
 3     CString strEdit;
 4     CString strNew;
 5     while (true)
 6     {
 7         Sleep(1000);
 8         WaitForSingleObject(g_hMutex,INFINITE);
 9         g_editShowInfo->GetWindowText(strEdit);
10         if(g_nTickNum > 0)
11         {
12             strNew.Format(_T("线程2:剩余票数:%d,售出1张票
"),g_nTickNum--);        
13             strEdit += strNew;
14             if(g_nTickNum == 0)
15             {
16                 strEdit += _T("线程2:票己售空,关闭售票窗口
");
17                 g_editShowInfo->SetWindowText(strEdit);
18                 ReleaseMutex(g_hMutex);
19                 break;
20             }
21             else
22             {
23                 g_editShowInfo->SetWindowText(strEdit);
24                 ReleaseMutex(g_hMutex);
25             }
26         }
27         else
28         {
29             strEdit += _T("线程2:票己售空,关闭售票窗口
");
30             g_editShowInfo->SetWindowTextW(strEdit);
31             ReleaseMutex(g_hMutex);
32             break;
33         }
34     }
35     return true;
36 }
线程_售票窗口2
 1 DWORD WINAPI ThreadSellTicket_Three(LPVOID lpParam)
 2 {
 3     CString strEdit;
 4     CString strNew;
 5     while (true)
 6     {
 7         Sleep(1000);
 8         WaitForSingleObject(g_hMutex,INFINITE);
 9         g_editShowInfo->GetWindowText(strEdit);
10         if(g_nTickNum > 0)
11         {
12             strNew.Format(_T("线程3:剩余票数:%d,售出1张票
"),g_nTickNum--);        
13             strEdit += strNew;
14             if(g_nTickNum == 0)
15             {
16                 strEdit += _T("线程3:票己售空,关闭售票窗口
");
17                 g_editShowInfo->SetWindowText(strEdit);
18                 ReleaseMutex(g_hMutex);
19                 break;
20             }
21             else
22             {
23                 g_editShowInfo->SetWindowText(strEdit);
24                 ReleaseMutex(g_hMutex);
25             }
26         }
27         else
28         {
29             strEdit += _T("线程3:票己售空,关闭售票窗口
");
30             g_editShowInfo->SetWindowTextW(strEdit);
31             ReleaseMutex(g_hMutex);
32             break;
33         }
34     }
35     return true;
36 }
线程_售票窗口3

 8.DestroyWindow添加相应代码

1 BOOL CMutexDemoDlg::DestroyWindow()
2 {
3     //关闭互斥体对象句柄
4     CloseHandle(g_hMutex);
5 
6     return CDialogEx::DestroyWindow();
7 }
DestroyWindow

最终演示效果如下:

原文地址:https://www.cnblogs.com/fzxiaoyi/p/12068465.html