多线程(六)多线程同步_SemaPhore信号量
信号量依然是一种内核同步对象,它的作用在于控制共享资源的最大访问数量
例如:我们有一个服务器,为这服务器创建一个线程池,线程池有五个线程,每个线程处理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. 定义相关全局变量和线程函数前置声明
1 // CSemaPhoreDlg 对话框 2 #define WM_UPDATAEDIT WM_USER+100 //自定义消息用来更新编辑框 3 4 HANDLE g_hSemaPhore; //信号对象句柄 5 HWND g_hwndMain; //当前窗口句柄 6 7 //线程函数前置声明 8 DWORD WINAPI Thread_One(LPVOID lpParam); 9 DWORD WINAPI Thread_Two(LPVOID lpParam); 10 DWORD WINAPI Thread_Three(LPVOID lpParam); 11 DWORD WINAPI Thread_Four(LPVOID lpParam); 12 DWORD WINAPI Thread_Five(LPVOID lpParam); 13 DWORD WINAPI Thread_Six(LPVOID lpParam); 14 DWORD WINAPI Thread_Seven(LPVOID lpParam); 15 DWORD WINAPI Thread_Eight(LPVOID lpParam); 16 DWORD WINAPI Thread_Nine(LPVOID lpParam); 17 DWORD WINAPI Thread_Ten(LPVOID lpParam);
4.窗口添加个消息响应_用于更新编辑框信息
1 afx_msg LRESULT CSemaPhoreDlg::OnUpdataedit(WPARAM wParam, LPARAM lParam) 2 { 3 4 m_richEdit.UpdateData(TRUE); 5 CString str; 6 m_richEdit.GetWindowText(str); 7 str += (LPCTSTR)wParam; 8 str += _T("\r"); 9 m_richEdit.SetWindowText(str); 10 11 //开始检测每一行文本,只要有"结束"字符串就把该行用红色显示 12 CHARFORMAT cf; 13 ZeroMemory(&cf, sizeof(CHARFORMAT)); 14 cf.cbSize = sizeof(CHARFORMAT); 15 cf.dwMask = CFM_COLOR; 16 cf.crTextColor = RGB(255, 0, 0); 17 18 CString strLine; 19 for (int i = 0; i<m_richEdit.GetLineCount(); i++) 20 { 21 m_richEdit.GetLine(i,strLine.GetBufferSetLength(32),32); 22 strLine.ReleaseBuffer(); 23 int nIndex = strLine.Find(_T("结束")); 24 if(nIndex!=-1) 25 { 26 int lineStart = m_richEdit.LineIndex(i);//取当前行第一个字符的索引 27 int lineEnd = lineStart + nIndex + 2; 28 m_richEdit.SetSel(lineStart, lineEnd);//选取第一行字符 29 m_richEdit.SetSelectionCharFormat(cf);//设置颜色 30 31 } 32 } 33 34 m_richEdit.SetSel(-1,-1); 35 m_richEdit.UpdateData(FALSE); 36 37 return 0; 38 }
5.OnInitDialog添加相关代码
1 BOOL CSemaPhoreDlg::OnInitDialog() 2 { 3 CDialogEx::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 //创建信号对象,初始和最大使用数量都为10 11 g_hSemaPhore = CreateSemaphore(NULL,3,3,NULL); 12 //获取主窗口句柄,供线程内部发送消息用以更新编辑框 13 g_hwndMain = this->m_hWnd; 14 //创建十个员工线程 15 CloseHandle(CreateThread(NULL,0,Thread_One,NULL,0,NULL)); 16 CloseHandle(CreateThread(NULL,0,Thread_Two,NULL,0,NULL)); 17 CloseHandle(CreateThread(NULL,0,Thread_Three,NULL,0,NULL)); 18 CloseHandle(CreateThread(NULL,0,Thread_Four,NULL,0,NULL)); 19 CloseHandle(CreateThread(NULL,0,Thread_Five,NULL,0,NULL)); 20 CloseHandle(CreateThread(NULL,0,Thread_Six,NULL,0,NULL)); 21 CloseHandle(CreateThread(NULL,0,Thread_Seven,NULL,0,NULL)); 22 CloseHandle(CreateThread(NULL,0,Thread_Eight,NULL,0,NULL)); 23 CloseHandle(CreateThread(NULL,0,Thread_Nine,NULL,0,NULL)); 24 CloseHandle(CreateThread(NULL,0,Thread_Ten,NULL,0,NULL)); 25 26 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 27 }
6.十个员工线程函数
1 //线程_员工1 2 DWORD WINAPI Thread_One(LPVOID lpParam) 3 { 4 WaitForSingleObject(g_hSemaPhore,INFINITE); 5 6 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工1:开始上网"),NULL); 7 //沿时3秒 8 Sleep(5000); 9 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工1:上网结束"),NULL); 10 11 //增加当前可使用计数 12 ReleaseSemaphore(g_hSemaPhore,1,NULL); 13 14 return TRUE; 15 } 16 //线程_员工2 17 DWORD WINAPI Thread_Two(LPVOID lpParam) 18 { 19 WaitForSingleObject(g_hSemaPhore,INFINITE); 20 21 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工2:开始上网"),NULL); 22 //沿时3秒 23 Sleep(5000); 24 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工2:上网结束"),NULL); 25 26 //增加当前可使用计数 27 ReleaseSemaphore(g_hSemaPhore,1,NULL); 28 29 return TRUE; 30 } 31 //线程_员工3 32 DWORD WINAPI Thread_Three(LPVOID lpParam) 33 { 34 WaitForSingleObject(g_hSemaPhore,INFINITE); 35 36 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工3:开始上网"),NULL); 37 //沿时3秒 38 Sleep(5000); 39 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工3:上网结束"),NULL); 40 41 //增加当前可使用计数 42 ReleaseSemaphore(g_hSemaPhore,1,NULL); 43 44 return TRUE; 45 } 46 //线程_员工4 47 DWORD WINAPI Thread_Four(LPVOID lpParam) 48 { 49 WaitForSingleObject(g_hSemaPhore,INFINITE); 50 51 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工4:开始上网"),NULL); 52 //沿时3秒 53 Sleep(5000); 54 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工4:上网结束"),NULL); 55 56 //增加当前可使用计数 57 ReleaseSemaphore(g_hSemaPhore,1,NULL); 58 59 return TRUE; 60 } 61 //线程_员工5 62 DWORD WINAPI Thread_Five(LPVOID lpParam) 63 { 64 WaitForSingleObject(g_hSemaPhore,INFINITE); 65 66 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工5:开始上网"),NULL); 67 //沿时3秒 68 Sleep(5000); 69 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工5:上网结束"),NULL); 70 71 //增加当前可使用计数 72 ReleaseSemaphore(g_hSemaPhore,1,NULL); 73 74 return TRUE; 75 } 76 //线程_员工6 77 DWORD WINAPI Thread_Six(LPVOID lpParam) 78 { 79 WaitForSingleObject(g_hSemaPhore,INFINITE); 80 81 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工6:开始上网"),NULL); 82 //沿时3秒 83 Sleep(5000); 84 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工6:上网结束"),NULL); 85 86 //增加当前可使用计数 87 ReleaseSemaphore(g_hSemaPhore,1,NULL); 88 89 return TRUE; 90 } 91 //线程_员工7 92 DWORD WINAPI Thread_Seven(LPVOID lpParam) 93 { 94 WaitForSingleObject(g_hSemaPhore,INFINITE); 95 96 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工7:开始上网"),NULL); 97 //沿时3秒 98 Sleep(5000); 99 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工7:上网结束"),NULL); 100 101 //增加当前可使用计数 102 ReleaseSemaphore(g_hSemaPhore,1,NULL); 103 104 return TRUE; 105 } 106 //线程_员工8 107 DWORD WINAPI Thread_Eight(LPVOID lpParam) 108 { 109 WaitForSingleObject(g_hSemaPhore,INFINITE); 110 111 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工8:开始上网"),NULL); 112 //沿时3秒 113 Sleep(5000); 114 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工8:上网结束"),NULL); 115 116 //增加当前可使用计数 117 ReleaseSemaphore(g_hSemaPhore,1,NULL); 118 119 return TRUE; 120 } 121 //线程_员工9 122 DWORD WINAPI Thread_Nine(LPVOID lpParam) 123 { 124 WaitForSingleObject(g_hSemaPhore,INFINITE); 125 126 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工9:开始上网"),NULL); 127 //沿时3秒 128 Sleep(5000); 129 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工9:上网结束"),NULL); 130 131 //增加当前可使用计数 132 ReleaseSemaphore(g_hSemaPhore,1,NULL); 133 134 return TRUE; 135 } 136 //线程_员工10 137 DWORD WINAPI Thread_Ten(LPVOID lpParam) 138 { 139 WaitForSingleObject(g_hSemaPhore,INFINITE); 140 141 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工10:开始上网"),NULL); 142 //沿时3秒 143 Sleep(5000); 144 SendMessage(g_hwndMain,WM_UPDATAEDIT,(WPARAM)_T("线程_员工10:上网结束"),NULL); 145 146 //增加当前可使用计数 147 ReleaseSemaphore(g_hSemaPhore,1,NULL); 148 149 return TRUE; 150 }
7.DestroyWindow
1 BOOL CSemaPhoreDlg::DestroyWindow() 2 { 3 CloseHandle(g_hSemaPhore); 4 5 return CDialogEx::DestroyWindow(); 6 }
最终演示效果如下:
分析下整个执行流程:
最大访问数量=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个线程可以苏醒