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

例如:我们有一个服务器,为这服务器创建一个线程池,线程池有五个线程,每个线程处理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条线程在执行时,其它线程就一直处于等待中

多线程(六)多线程同步_SemaPhore信号量的更多相关文章

  1. 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEvent, AutoResetEvent

    [源码下载] 重新想象 Windows 8 Store Apps (47) - 多线程之线程同步: Semaphore, CountdownEvent, Barrier, ManualResetEve ...

  2. C# 多线程六之Task(任务)三之任务工厂

    1.知识回顾,简要概述 前面两篇关于Task的随笔,C# 多线程五之Task(任务)一 和 C# 多线程六之Task(任务)二,介绍了关于Task的一些基本的用法,以及一些使用的要点,如果都看懂了,本 ...

  3. c#中多线程间的同步

    目录 一.引入 二.Lock 三.Monitor 四.Interlocked 五.Semaphore 六.Event 七.Barrier 八.ReaderWriterLockSlim 九.Mutex ...

  4. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  5. 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)

    数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...

  6. C# 多线程之线程同步

    多线程间应尽量避免同步问题,最好不要线程间共享数据.如果必须要共享数据,就需要使用同步技术,确保一次只有一个线程访问和改变共享状态. 一::lock语句 lock语句事设置锁定和接触锁定的一种简单方法 ...

  7. 【2017-06-20】Linux应用开发工程师C/C++面试问题记录之一:Linux多线程程序的同步问题

    参考之一:Linux 线程同步的三种方法 链接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 简要回答: Linux下线程同步最常用的三种方法就是互斥 ...

  8. C#多线程之线程同步篇3

    在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...

  9. C#多线程之线程同步篇1

    在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...

随机推荐

  1. ffmpeg-python 任意提取视频帧

    ▶ 环境准备 1.安装 FFmpeg 音/视频工具 FFmpeg 简易安装文档 2.安装 ffmpeg-python pip3 install ffmpeg-python 3.[可选]安装 openc ...

  2. vue/cli2.0优化

    vue/cli2.0 脚手架 在项目写完了之后, 运行npm run build --report可以看出这个项目的资源占比情况.可以看出整个项目哪一个资源在整个项目占比最大.它会自动打开一个可视化的 ...

  3. acwing 116. 飞行员兄弟

    地址  https://www.acwing.com/problem/content/118/ “飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有16个把手的冰箱. 已知每个把手可以处于以下两种状态之 ...

  4. CF785D Anton and School - 2

    题目链接 problem 给出一个括号序列,要求删除一些括号使得剩下的括号序列是个匹配的括号序列,且改括号序列左边全部为左括号,右边全部为右括号. solution 考虑枚举左右括号交界的位置\(x\ ...

  5. Codeforces Round #599 (Div. 2) B2. Character Swap (Hard Version) 构造

    B2. Character Swap (Hard Version) This problem is different from the easy version. In this version U ...

  6. npm查看本地包版本号和远程包的版本号

    npm 查看远程包 第一种方法: npm info <packageName> 第二种方法: npm view <packageName> versions --json np ...

  7. RST Methodology: “Responsible Tester”

    翻译另一篇James Bach的关于快速软件测试的文章,原文链接:http://www.satisfice.com/blog/archives/1364 在快速软件测试方法论中,我们区分出三种主要角色 ...

  8. vue发送ajx请求 axios

    一. 简介 1.vue本身不支持发送AJAX请求,需要使用vue-resource(vue1.0版本).axios(vue2.0版本)等插件实现 2.axios是一个基于Promise的HTTP请求客 ...

  9. 五分钟搞定 HTTPS 配置,二哥手把手教

    01.关于 FreeSSL.cn FreeSSL.cn 是一个免费提供 HTTPS 证书申请.HTTPS 证书管理和 HTTPS 证书到期提醒服务的网站,旨在推进 HTTPS 证书的普及与应用,简化证 ...

  10. swoole中使用task进程异步的处理耗时任务

    我们知道,swoole中有两大进程,分别是 master 主进程和 manager 管理进程. 其中 master 主进程中会有一个主 reactor 线程和多个 reactor 线程,主要的作用就是 ...