多线程(六)多线程同步_SemaPhore信号量
阅读原文时间:2023年07月12日阅读:1

信号量依然是一种内核同步对象,它的作用在于控制共享资源的最大访问数量

例如:我们有一个服务器,为这服务器创建一个线程池,线程池有五个线程,每个线程处理1个请求。当五个线程都在处理请求时,这个线程池己到达使用上限,

可使用数量为0,无法再处理其它请求。此时又有新的请求到来,新的请求将被放入缓存中进行等待。当有线程处理完请求退出时,线程池可使用数量+1。

期间线程池会一直判断可使用数量是否大于0并且小于最大使用数量5?如果为真,会查找缓存中是否有等待的请求,如果有就取出一个请求进行处理。

整个线程池周而复始执行相应操作,始终保持着同时只能处理五个请求。

包含三个部份:

使用计数器:所有内存对象都有这个属性

最大资源计数器: 控制所能访问资源的最大数量

当前资源计数器: 当前可以使用的资源数量

注意:当前资源计数永远不可能大于最大资源计数,也不会是个负数。当前资源计数为0时,信号量是未触发状态。当前资源计数大于0时,信号是触发状态

线程中调用等待函数时,等待函数内部会检查当前资源计数器,如果发现当前资源计数器为0是未触发状态,线程就会处于等待状态。

如果发现当前资源计数器>0,就会把资源计数器-1,然后使当前线程变为可调度状态。线程执行完相关代码后需要调用ReleaseSemphore增加当前可以使用的资源数量

创建信号对象:

HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,LONG lInitialCount,LONG lMaximumCount,PCTSTR pszName);

1.psa   指向安全属性指针,一般为NULL

2. lInitialCount   初始时有多少资源可供使用,一般=lMaximumCount

3. lMaximumCount  所能访问资源的最大数量

4.pszName  信号对象名称,如果不跨进程使用,一般为NULL

返回值:

成功返回新信号对象句柄,失败返回0

释放信号对象:增加当前可以使用的资源数量

BOOL ReleaseSemaphore(HANDLE hsem, LONG lReleaseCount,PLONG plPreviousCount);

1.hsem  信号对象句柄

2.lReleaseCount  改变的计数值,一般为1,当前资源计数器会+1

3.plPreviousCount  返回当前资源数量的原始值,一般为NULL

编写一个Demo用于演示SemaPhore基本操作

功能介绍:

模拟公司员工上网系统,一共有10名员工,同时只允许3名员工上网,每人上网时间为5秒

开始编写代码:

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

2. 添加一个富文本框用于显示信息,修改属性支持多行,并且为只读. 然后绑定一个变量

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

// CSemaPhoreDlg 对话框
#define WM_UPDATAEDIT WM_USER+100 //自定义消息用来更新编辑框

HANDLE g_hSemaPhore; //信号对象句柄
HWND g_hwndMain; //当前窗口句柄

//线程函数前置声明
DWORD WINAPI Thread_One(LPVOID lpParam);
DWORD WINAPI Thread_Two(LPVOID lpParam);
DWORD WINAPI Thread_Three(LPVOID lpParam);
DWORD WINAPI Thread_Four(LPVOID lpParam);
DWORD WINAPI Thread_Five(LPVOID lpParam);
DWORD WINAPI Thread_Six(LPVOID lpParam);
DWORD WINAPI Thread_Seven(LPVOID lpParam);
DWORD WINAPI Thread_Eight(LPVOID lpParam);
DWORD WINAPI Thread_Nine(LPVOID lpParam);
DWORD WINAPI Thread_Ten(LPVOID lpParam);

相关全局变量和线程函数的前置声明

4.窗口添加个消息响应_用于更新编辑框信息

afx_msg LRESULT CSemaPhoreDlg::OnUpdataedit(WPARAM wParam, LPARAM lParam)
{

 m\_richEdit.UpdateData(TRUE);  
 CString str;  
 m\_richEdit.GetWindowText(str);  
 str += (LPCTSTR)wParam;  
 str += \_T("\\r");  
 m\_richEdit.SetWindowText(str);

 //开始检测每一行文本,只要有"结束"字符串就把该行用红色显示  
 CHARFORMAT cf;  
 ZeroMemory(&cf, sizeof(CHARFORMAT));  
 cf.cbSize = sizeof(CHARFORMAT);  
 cf.dwMask = CFM\_COLOR;  
 cf.crTextColor = RGB(, , );

 CString strLine;  
 for (int i = ; i<m\_richEdit.GetLineCount(); i++)  
 {  
     m\_richEdit.GetLine(i,strLine.GetBufferSetLength(),);  
     strLine.ReleaseBuffer();  
     int nIndex = strLine.Find(\_T("结束"));  
     if(nIndex!=-)  
     {  
         int lineStart = m\_richEdit.LineIndex(i);//取当前行第一个字符的索引  
         int lineEnd = lineStart + nIndex + ;  
         m\_richEdit.SetSel(lineStart, lineEnd);//选取第一行字符  
         m\_richEdit.SetSelectionCharFormat(cf);//设置颜色

     }  
 }

 m\_richEdit.SetSel(-,-);  
 m\_richEdit.UpdateData(FALSE);

 return ;  

}

消息响应_用于更新编辑框信息

5.OnInitDialog添加相关代码

BOOL CSemaPhoreDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动  
 //  执行此操作  
 SetIcon(m\_hIcon, TRUE);            // 设置大图标  
 SetIcon(m\_hIcon, FALSE);        // 设置小图标

 //创建信号对象,初始和最大使用数量都为10  
 g\_hSemaPhore = CreateSemaphore(NULL,,,NULL);  
 //获取主窗口句柄,供线程内部发送消息用以更新编辑框  
 g\_hwndMain = this->m\_hWnd;  
 //创建十个员工线程  
 CloseHandle(CreateThread(NULL,,Thread\_One,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Two,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Three,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Four,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Five,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Six,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Seven,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Eight,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Nine,NULL,,NULL));  
 CloseHandle(CreateThread(NULL,,Thread\_Ten,NULL,,NULL));

 return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE  

}

OnInitDialog

6.十个员工线程函数

//线程_员工1
DWORD WINAPI Thread_One(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工1:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工1:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工2
DWORD WINAPI Thread_Two(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工2:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工2:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工3
DWORD WINAPI Thread_Three(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工3:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工3:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工4
DWORD WINAPI Thread_Four(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工4:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工4:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工5
DWORD WINAPI Thread_Five(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工5:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工5:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工6
DWORD WINAPI Thread_Six(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工6:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工6:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工7
DWORD WINAPI Thread_Seven(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工7:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工7:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工8
DWORD WINAPI Thread_Eight(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工8:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工8:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工9
DWORD WINAPI Thread_Nine(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工9:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工9:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}
//线程_员工10
DWORD WINAPI Thread_Ten(LPVOID lpParam)
{
WaitForSingleObject(g_hSemaPhore,INFINITE);

 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工10:开始上网"),NULL);  
 //沿时3秒  
 Sleep();  
 SendMessage(g\_hwndMain,WM\_UPDATAEDIT,(WPARAM)\_T("线程\_员工10:上网结束"),NULL);

 //增加当前可使用计数  
 ReleaseSemaphore(g\_hSemaPhore,,NULL);

 return TRUE;  

}

十个员工线程函数

7.DestroyWindow

BOOL CSemaPhoreDlg::DestroyWindow()
{
CloseHandle(g_hSemaPhore);

 return CDialogEx::DestroyWindow();  

}

DestroyWindow

最终演示效果如下:

分析下整个执行流程:

最大访问数量=3,  当前可访问资源数量=3

线程_员工2:开始上网  //当前可访问资源数量=2
线程_员工1:开始上网  //当前可访问资源数量=1
线程_员工3:开始上网  //当前可访问资源数量=0    为0此时信号量对象为未触发状态,所有等待该信号量的线程都处于等待
线程_员工2:上网结束  //当前可访问资源数量=1    不为0时,此时信号量对象为触发状态,等待的线程中有1个线程可以苏醒
线程_员工3:上网结束  //当前可访问资源数量=2   此时有2个线程可以苏醒
线程_员工1:上网结束 //当前可访问资源数量=3    此时有3个线程可以苏醒
线程_员工4:开始上网 //当前可访问资源数量=2    此时还有2个线程可以苏醒
线程_员工5:开始上网 //当前可访问资源数量=1    此时还有1个线程可以苏醒
线程_员工6:开始上网 //当前可访问资源数量=0     为0此时信号量对象为未触发状态,所有等待该信号量的线程都处于等待
线程_员工4:上网结束 //当前可访问资源数量=1    不为0时,此时信号量对象为触发状态,等待的线程中有1个线程可以苏醒
线程_员工5:上网结束 //当前可访问资源数量=2   此时有2个线程可以苏醒
线程_员工7:开始上网 //当前可访问资源数量=1   此时还有1个线程可以苏醒
线程_员工6:上网结束  //当前可访问资源数量=2 此时有2个线程可以苏醒
线程_员工8:开始上网   //当前可访问资源数量=1   此时还有1个线程可以苏醒
线程_员工9:开始上网  //当前可访问资源数量=0     为0此时信号量对象为未触发状态,所有等待该信号量的线程都处于等待
线程_员工7:上网结束  //当前可访问资源数量=1    不为0时,此时信号量对象为触发状态,等待的线程中有1个线程可以苏醒
线程_员工10:开始上网 //当前可访问资源数量=0     为0此时信号量对象为未触发状态,所有等待该信号量的线程都处于等待
线程_员工8:上网结束   //当前可访问资源数量=1    不为0时,此时信号量对象为触发状态,等待的线程中有1个线程可以苏醒
线程_员工9:上网结束   //当前可访问资源数量=2   此时有2个线程可以苏醒
线程_员工10:上网结束   //当前可访问资源数量=2   此时有3个线程可以苏醒

从上面就可以看得出来,无论任何时候当前可访问资源数量都是>0 并且 小于 最大访问数量,并且始终最多只会有3条线程在运行,当有3条线程在执行时,其它线程就一直处于等待中