本篇使用信号量机制实现对全局资源的正确使用,包括以下两点:

  • 各个子线程对全局资源的互斥使用
  • 主线程对子线程的同步

信号量

简单的说,信号量内核对象,也是多线程同步的一种机制,它可以对资源访问进行计数,包括最大资源计数和当前资源计数,是两个32位的值;另外,计数是以原子访问的方式进行,由操作系统维护;

  • 最大资源计数,表示可以控件的最大资源数量
  • 当前资源计数,表示当前可用资源的数量

信号量的规则:

  • 如果当前资源计数器大于0,那么信号量处于触发状态
  • 如果当前资源计数器等于0,那么信号量处于未触发状态
  • 系统绝对不会让当前资源计数器变为负数
  • 当前资源计数器决定不会大于最大资源计数

信号量机制:

以一个停车场的运作为例。假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用,当当前资源计数大于0,信号量处于触发状态,线程编程可调度;

相关API

  • 创建信号量
HANDLE WINAPI CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//more设置为NULLLONG lInitialCount,   //当前可用资源的数量LONG lMaximumCount,   //最大资源数量LPCTSTR lpName        //信号量名字,可以设置为NULL
);
  • 释放信号量
BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,      //信号量句柄LONG lReleaseCount,     //该函数返回时,当前可用资源的数增加lReleaseCount个LPLONG lpPreviousCount
);
  • 打开已经存在的信号量
HANDLE WINAPI OpenSemaphore(DWORD dwDesiredAccess,BOOL bInheritHandle,LPCTSTR lpName //打开信号量的名字
);
  • wait函数
DWORD WINAPI WaitForSingleObject(HANDLE hHandle,      //当等待成功时,当前可用信号量递减DWORD dwMilliseconds //等待时间
);

线程同步举例

线程同步包含两层含义:

  1. 对全局资源互斥访问
  2. 线程间的有序、协同执行(比如:线程A执行完后,再进行线程B的操作)

例子1 互斥访问

#include "stdafx.h"
#include <iostream>
#include <afxmt.h>
using namespace std;int g_nIndex = 0;
const int nMaxCnt = 20;#define MAX_SEM_COUNT 1
#define THREADCOUNT 12HANDLE ghSemaphore;DWORD WINAPI ThreadProcBySEM( LPVOID );int _tmain(int argc, _TCHAR* argv[])
{HANDLE aThread[THREADCOUNT];int i;// Create a semaphore with initial and max counts of MAX_SEM_COUNT// 备注:实现多个子线程资源互斥范围,最大信号量需要设置为1ghSemaphore = CreateSemaphore( NULL,   // default security attributes1,      // initial count1,      // maximum countNULL);  // unnamed semaphoreif (ghSemaphore == NULL) {printf("CreateSemaphore error: %d\n", GetLastError());return 0;}// Create worker threadsfor( i=0; i < THREADCOUNT; i++ ){aThread[i] = CreateThread( NULL,       // default security attributes0,          // default stack size(LPTHREAD_START_ROUTINE) ThreadProcBySEM, NULL,       // no thread function arguments0,          // default creation flagsNULL); // receive thread identifierif( aThread[i] == NULL ){printf("CreateThread error: %d\n", GetLastError());return 0;}}//Wait for all threads to terminateWaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);//Close thread and semaphore handlesfor( i=0; i < THREADCOUNT; i++ )CloseHandle(aThread[i]);CloseHandle(ghSemaphore);return 0;
}DWORD WINAPI ThreadProcBySEM(LPVOID lpParam)
{DWORD dwWaitResult;BOOL bContinue = TRUE;while(bContinue){//Try to enter the semaphore gate.dwWaitResult = WaitForSingleObject(ghSemaphore,INFINITE);if (WAIT_OBJECT_0 == dwWaitResult){if (g_nIndex++ < nMaxCnt){//Simulate thread spending time on taskSleep(5);printf("Thread %d: Index = %d\n", GetCurrentThreadId(), g_nIndex);}else{bContinue = FALSE;}// Relase the semaphore when task is finishedif (!ReleaseSemaphore( ghSemaphore,  // handle to semaphore1,            // increase count by oneNULL))       // not interested in previous count{printf("ReleaseSemaphore error: %d\n", GetLastError());}}else{break;}}return TRUE;
}

运行结果:

例子2 线程同步

下面我们将线程创建时的顺序编号,也打印出来,实现方式是将变量i传递给线程,以实现打印;

错误代码:

我们将编号i的地址作为CreateThread入参,传递给线程函数ThreadProcBySEM;主要修改如下:

 // Create worker threadsfor( i=0; i < THREADCOUNT; i++ ){aThread[i] = CreateThread( NULL,       // default security attributes0,          // default stack size(LPTHREAD_START_ROUTINE) ThreadProcBySEM, &i,         //线程编号地址0,          // default creation flagsNULL);      // receive thread identifierif( aThread[i] == NULL ){printf("CreateThread error: %d\n", GetLastError());return 0;}}//线程函数代码:
DWORD WINAPI ThreadProcBySEM(LPVOID lpParam)
{DWORD dwWaitResult;BOOL bContinue = TRUE;if (NULL == lpParam){return FALSE;}//获取编号,线程创建有时间开销,当函数执行到,赋值语句时//lpParam所指向的是变量i,但是主线程已经对i进行++,不再是创建时的值了int nThreadNum = *(int*)lpParam;if (WAIT_OBJECT_0 == dwWaitResult){if (g_nIndex++ < nMaxCnt){//Simulate thread spending time on taskSleep(5);//打印线程编号printf("NO %d = Thread %d: Index = %d\n", nThreadNum, GetCurrentThreadId(), g_nIndex);}}
}

运行结果:

从下图中可以看出,不同的线程句柄,却有相同的线程编号,属于异常线程;

原因分析:

线程创建到线程函数执行存在时间开销,线程函数不会第一时间开始执行,而此时主线程还处于非阻塞状态,主线程仍然可以对变量i不断进行++操作,导致当前线程进行访问时,其内容已经不再是当前那个值了;

正确做法:

1.引入关键代码段同步对象,用于各个子线间,访问全局资源g_nIndex,
2.在创建新线程前,需要将当前线程的编号保存到,临时变量;
3.将信号量对象用于主线程和子线程间的同步;

全部代码如下:

#include "stdafx.h"
//#include <windows.h>
#include <iostream>
#include <afxmt.h>
using namespace std;#define MAX_SEM_COUNT 1
#define THREADCOUNT 12int g_nIndex = 0;
const int nMaxCnt = 20;HANDLE ghSemaphore;
CRITICAL_SECTION g_csLockA;DWORD WINAPI ThreadProcBySEM( LPVOID );int _tmain(int argc, _TCHAR* argv[])
{HANDLE aThread[THREADCOUNT];int i;// Create a semaphore with initial and max counts of MAX_SEM_COUNTghSemaphore = CreateSemaphore( NULL,   // default security attributes0,      // initial count,modify:当前处于非触发状态1,      // maximum countNULL);  // unnamed semaphoreif (ghSemaphore == NULL) {printf("CreateSemaphore error: %d\n", GetLastError());return 0;}//初始化关键代码段对象,用于访问全局资源的互斥InitializeCriticalSection(&g_csLockA);// Create worker threadsfor( i=0; i < THREADCOUNT; i++ ){aThread[i] = CreateThread( NULL,       // default security attributes0,          // default stack size(LPTHREAD_START_ROUTINE) ThreadProcBySEM, &i,       // no thread function arguments0,          // default creation flagsNULL); // receive thread identifierif( aThread[i] == NULL ){printf("CreateThread error: %d\n", GetLastError());return 0;}//modify 创建新线程前,需要将编号保存起来,才创建新线程//用于同步主线程和子线程WaitForSingleObject(ghSemaphore,INFINITE);}//Wait for all threads to terminateWaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);//Close thread and semaphore handlesfor( i=0; i < THREADCOUNT; i++ )CloseHandle(aThread[i]);CloseHandle(ghSemaphore);DeleteCriticalSection(&g_csLockA);return 0;
}DWORD WINAPI ThreadProcBySEM(LPVOID lpParam)
{DWORD dwWaitResult;BOOL bContinue = TRUE;if (NULL == lpParam){return FALSE;}int nThreadNum = *(int*)lpParam;// Relase the semaphore if (!ReleaseSemaphore( ghSemaphore,  // handle to semaphore1,            // increase count by oneNULL))       // not interested in previous count{printf("ReleaseSemaphore error: %d\n", GetLastError());}while(bContinue){Sleep(10);//modify:当前线程处于休眠,给其他线程执行机会//Try to enter the cs gate. //modify:保护资源的唯一性访问EnterCriticalSection(&g_csLockA);if (g_nIndex++ < nMaxCnt){printf("NO %02d = Thread %d: Index = %d\n", nThreadNum, GetCurrentThreadId(), g_nIndex);}else{bContinue = FALSE;}LeaveCriticalSection(&g_csLockA);}return TRUE;
}

运行结果:

从运行结果看出,线程句柄和编号可以一一对应,并且资源访问也正常;

转载于:https://www.cnblogs.com/jinxiang1224/p/8468287.html

利用信号量实现线程同步相关推荐

  1. 实验三 使用POSIX信号量实现线程同步

    实验三 使用POSIX信号量实现线程同步 目录 实验三 使用POSIX信号量实现线程同步 实验环境 一.实验目的 二.实验内容 三.实验步骤 四.实验总结 实验环境 操作系统版本:ubuntu-14. ...

  2. java中用关键字为共享资源加锁_利用synchronized实现线程同步的案例讲解

    一.前期基础知识储备 (1)线程同步的定义:多线程之间的同步. (2)多线程同步原因:一个多线程的程序如果是通过Runnable接口实现的,则意味着类中的属性将被多个线程共享,由此引出资源的同步问题, ...

  3. mfc通过信号量保证线程同步

    1.声明一个全局handle,记住在cpp里也声明 extern HANDLE uiHandle; 2.创建信号量 uiHandle = CreateSemaphore(NULL,1,1,NULL); ...

  4. C#并行编程(6):线程同步面面观

    理解线程同步 线程的数据访问 在并行(多线程)环境中,不可避免地会存在多个线程同时访问某个数据的情况.多个线程对共享数据的访问有下面3种情形: 多个线程同时读取数据: 单个线程更新数据,此时其他线程读 ...

  5. 铂金04:令行禁止-为何说信号量是线程间的同步利器

    欢迎来到<并发王者课>,本文是该系列文章中的第17篇. 在并发编程中,信号量是线程同步的重要工具.在本文中,我将带你认识信号量的概念.用法.种类以及Java中的信号量. 信号量(Semap ...

  6. linux线程基础篇----线程同步与互斥

    linux线程基础----线程同步与互斥 一.同步的概念 1.同步概念  所谓同步,即同时起步,协调一致.不同的对象,对"同步"的理解方式略有不同.如,设备同步,是指在两个设备   ...

  7. 线程同步与异步套接字编程

    1.利用事件对象来实现线程间的同步 新建一个win32 console application,取名Event,再建一个Event源文件,编辑: #include <iostream.h> ...

  8. 多线程怎么保证数据安全_Python threading实现多线程 提高篇 线程同步,以及各种锁...

    本文主要讲多线程的线程之间的资源共享怎么保持同步. 多线程基础篇见,木头人:Python threading实现多线程 基础篇 Python的多线程,只有用于I/O密集型程序时效率才会有明显的提高,如 ...

  9. java 信号量 互斥锁_线程同步(互斥锁与信号量的作用与区别)

    "信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里).而互斥锁是用在多线程多任务互斥的,一 ...

最新文章

  1. matlab-等高线图-三维曲线的绘制
  2. 此上下文中不支持函数定义。请在代码文件中创建函数。_深入解析Python上下文管理器,让你不再迷茫!...
  3. 数据结构算法 二进制转十进制_数据结构 - 栈
  4. 手工画设计模式的类图
  5. su封面插件_这届SU太优秀,一张纸建出一座音乐厅?
  6. python是什么类型的语言-python到底是什么类型的语言
  7. 字符串匹配算法知多少?
  8. 常数除以0的极限是什么_【高数总结求极限方法】百度作业帮
  9. linux终端ANSI转义字符
  10. 数据结构算法——1097. Hub Connection plan
  11. 在BREW中打造自己的GUI(8)-IWEB的封装
  12. 立创EDA导出Altium Designer的pcb文件没有没有显示飞线
  13. Graph Visualization and Navigation in Information Visualization: A Survey 译文
  14. Crawlab(crawlab github)
  15. 网易(weather)天气预报接口
  16. Android中播放本地SD卡中歌曲需要的添加的权限
  17. 算法刷题 -- 1823. 找出游戏的获胜者<难度 ★★☆>
  18. 不正确的c语言语句是,【单选题】下列不正确的C语言语句是( )。 A. x=y=5; B. x=1,y=2; C. y=int x; D. x++;...
  19. 熔断器 java_SpringCloud之熔断器使用(Hystrix)
  20. 在抖音及一些直播上,如何进行违禁词在线检测呢?

热门文章

  1. Android 加入一个动作按钮
  2. 370万开发者,14万家企业!飞桨中国行落地深圳 激发AI软硬件创新发展新动能...
  3. 压缩之后神经网络忘记了什么?Google研究员给出了答案
  4. 速进!2000核实计算资源免费领取,名额有限,即开即送!
  5. 3D-BoNet:比3D点云实例分割算法快10倍!代码已开源
  6. Python知识点之Python面向对象
  7. easyx鼠标放置前按钮颜色_七种正确使用鼠标的好习惯,让你摆脱鼠标手的痛苦...
  8. 从V1到V4,让你读懂YOLO原理——深度AI科普团队
  9. 最先进单插槽专业绘图解决方案
  10. 基础知识(一)matlab与c++混合编程之环境搭建