本笔记整理自:《Windows核心编程(第五版)》

什么是线程同步

  • 多个线程是并行运行的,而在对堆区的变量是公有变量,任何线程都可以对他们进行访问和修改。这就会引发x访问冲突的问题。当多个线程同时修改一个变量时,极有可能会产生逻辑上的错误,甚至程序崩溃。

用户方式中的线程同步

原子访问:Interlocked系列函数

InterlockedIncrement(LONG volatile *Addend)                                             // *Addend++;
InterlockedDecrement(LONG volatile *Addend) // *Addend--;
InterlockedExchangeAdd(LONG volatile *Addend,LONG Value) // *Addend+=Value;
InterlockedExchangeSubtract(LONG volatile *Addend,LONG Value) // *Addend-=Value;
InterlockedExchange(LONG volatile *Target,LONG Value) // *Target=Value;
TInterlockedExchangePointer(PVOID volatile *Target,PVOID Value) // *Target=&Value;
InterlockedCompareExchange(LONG volatile *Target,LONG Exchange,Long Compared) // if(*Target==Compared) *pDest=Exchange;
InterlockedCompareExchangePointer(PVOID volatile *Target,PVOID Exchange,PVOID Compared) // if(*pDest==pCompare) pDest=&value;

CRITICAL_SECTION:关键段

  • 是一种实现原子操作的较为简单的方式
  • 相关用法
//Samples:
CRITICAL_SECTION g_cs;
InitializeCriticalSection(&g_cs);
void thread_enter_function()
{
EnterCriticalSection(&g_cs); //访问线程共享的变量
//在此范围内,涉及到的数据只允许一个线程使用
//To-DO:... LeaveCriticalSection(&g_cs);
}
  • 相关函数
//初始化
VOID InitializeCriticalSection(PCRITICAL_SECTION* pcs); //删除变量。当不需要这一结构体时,就可以调用此方法删除此变量
VOID DeleteCriticalSection(PCRITICAL_SECTION* pcs); // 是否允许访问,可以用此函数代替EnterCriticalSection
// 每一个返回true的TryEnterCriticalSection的调用必须搭配一个LeaveCriticalSection
// 非挂起式关键段访问
// 若有其他线程访问此关键段,则返回FALSE。可以访问则放回TRUE
BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs); //进入关键段(当有其他在访问时会挂起)
VOID EnterCriticalSection(PCRITICAL_SECTION pcs); //离开关键段
VOID LeaveCriticalSection(PCRITICAL_SECTION pcs); //设置挂起前试图访问锁的次数
//也就是说:若当前有其他地方正在访问关键段时,此处持续访问的次数,若超过这一次数,此处将会挂起。
SetCriticalSectionSpinCount(PCRITICAL_SECTION pcs,DWORD dwSpinCount); //设置挂起前试图访问锁的次数并初始化变量
InitializeCriticalSectionAndSpinCount(PCRITICAL_SECTION pcs,DWORD dwSpinCount);

内核对象的同步方式

  • 内核对象的同步是用什么来实现原子访问的呢?关键函数就是等待函数。
/*
* @params:
* hObject:要等待的内核对象
* dwMilliseconds:线程最多愿意花多长的时间来等待对象被触发。可以设为INFINITE来表示无限长的时间
*
* @return:
* 指定了当前的状态
* WAIT_OBJECT_0 : 表示等待的对象被触发
* WAIT_TIMEOUT : 表示等待对象超过了dwMilliseconds
* WAIT_FAILED : 给WaitForSingleObject传入了无效参数
*/
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds); /*
* 和WaitForSingleObject类似,区别在于此函数可以同时检查多个内核对象的触发情况
*
* @params:
* dwCount : 希望函数检查内核对象的数量,范围是 [1 , MAXIMUM_WAIT_OBJECTS]
* phObjects : 内核对象数组
* bWaitAll : 是否等待所有内核对象触发才取消堵塞(为false时只要有一个触发就取消堵塞)
* dwMilliseconds:线程最多愿意花多长的时间来等待对象被触发。可以设为INFINITE来表示无限长的时间
*
* @return
* 和WaitForSingleObject的区别在于:
* WAIT_OBJECT_0 : 表示等待的对象被触发1个
* WAIT_OBJECT_1 : 表示等待的对象被触发2个
* ...
* bWaitAll设为false,正常情况下返回[ 1 , WAIT_OBJECT_0+(dwCount-1) ]
*
* PS:如果bWaitAll设为true,则返回WAIT_OBJECT_0
*/
DWORD WaitForMultipleObjects(DWORD dwCount,CONST HANDLE* phObjects,BOOL bWaitAll,DWORD dwMilliseconds);

基于此等待函数,下面将介绍四种内核对象。

事件内核对象

  • 事件内核是最基本的对象。它主要管理:

    • 一个使用计数
    • 是否是人工重置事件的布尔值
    • 是否是触发状态的布尔值
  • 人工重置事件VS自动重置事件
    • 人工重置事件:得到通知时,等待时间的所有线程均变为可调度线程。
    • 自动重置事件:得到通知时,等待该事件的线程只有一个变为可调度线程。系统会自动将事件对象状态设置为未激活状态
  • 相关函数
/*
* @params
* psa:安全性结构体
* bManualReset : 创建的是一个手动事件(TRUE)还是自动重置事件(FALSE)
* bInitialState : 初始的状态是触发(TRUE)还是未触发(FALSE)
* pszName:事件名称(唯一标识字符串)
*/
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL bManualReset,BOOL bInitialState,PCTSTR pszName); /*
* 打开已存在的事件。(供其他线程访问此事件)
* @params
* dwDesireAccess:(int)指定对事件对象的请求访问权限,如果安全描述符指定的对象不允许要求通过对调用该函数的过程,函数将返回失败
* hInheriy:是否继承
* pszName:事件名称
*/
HANDLE OpenEvent(DWORD dwDesireAccess,BOOL hInheriy,PCTSTR pszName); BOOL SetEvent(HANDLE hEvent); //把事件设置为触发状态
BOOL ResetEvent(HANDLE hEvent); //把事件设置为未触发状态
BOOL PulseEvent(HANDLE hEvent); //触发一次或设置为未触发,相当于激发一次。(不常用)

可等待的计时器内核对象

在某个事件或按规定的间隔事件发出自己的信号通知的内核对象。

  • 相关函数
/*
* @params:
* bManualReset:是手动重置计时器还是自动重置计时器
*/
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL bManualReset,PCTSTR pszName) //打开已存在的计时器内核对象
HANDLE OpenWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL bManualReset,PCTSTR pszName) /*
* @params
* hTimer : 想要触发的计时器
* pDueTime : 第一次触发的事件的时间
* lPeriod :在第一次触发之后,计时器应该以怎样的频度触发。(单位为毫秒)
* pfnCompletionRoutine : 可设为NULL
* pvArgToCompletionRoutine : 可设为NULL
* bResume : 可设为false
*/
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime;
LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL bResume
); //取消计时器
BOOL CancelWaitableTimer(HANDLE hTimer);

信号量内核对象

  • 用来对资源进行计数。它的规则如下:

    • 如果当前资源计数>0,那么信号量处于触发状态。
    • 如果当前资源计数=0,那么信号量处于未触发状态
    • 在线程中调用一个等待函数,如果>0,则计数器-1,线程取消阻塞。
  • 相关函数
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTES psa,LONG lInitialCount,LONG lMaximumCount,PCTSTR pszName);

HANDLE OpenSemaphore(DWORD dwDesiredAccess,BOOL bInheritHandle,PCTSTR pszName);

/*
* @params:
* hSemaphore : 信号量内核对象句柄
* lReleaseCount : 增加的计数值
* plPreviousCount : 增加前的计数值
*/
BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,PLONG plPreviousCount)

互斥量内核对象

  • 用来确保一个线程独占对一个资源的访问。
  • 包含一个实用技术、线程ID以及一个递归计数
  • 互斥量规则如下
    • 如果线程ID为0(无效线程ID),那么该无耻两不为任何线程所占有,它处于触发状态
    • 如果线程ID非0,那么有一个线程已经占用了该互斥量,它处于未触发状态。
    • 操作系统对互斥量进行了特殊处理
  • 等待函数不在返回WAIT_OBJECT0,而是返回特殊的值WAIT_ABANDONED
  • 互斥量和关键段的比较
特征 互斥量 关键段
性能
是否能跨进程使用
声明 HANDLE hmtx; CRITICAL_SECTION cs;
初始化方式 hmtx=CreateMutex(NULL,FALSE,NULL); InitializeCriticalSection(&cs);
清理 CloseHandle(hmtx); DeleteCriticalSection(&cs);
无限等待 WaitForSingleObject (hmtx, INFINITE); EnterCriticalSection(&cs);
0等待 WaitForSingleObject (hmtx, 0); TryEnterCriticalSection(&cs);
任意长时间的等待 WaitForSingleObject (hmtx, timeLength) 不支持
释放 ReleaseMutex(hmtx); LeaveCriticalSection(&cs);
是否能同时等待其他内核对象 是(WaitForMultipleObjects或其他)
  • 相关函数
/*
* @params
* bInitialOwner : 控制互斥量的初始化状态,
* FALSE :无占用
* TRUE:占用的线程为当前线程
*/
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa;
BOOL bInitialOwner,
PCTSTR pszName
); HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInitialOwner,
PCTSTR pszName
); BOOL ReleaseMutex(HANDLE hMutex);

Windows编程之线程同步的更多相关文章

  1. .NET面试题解析(07)-多线程编程与线程同步

      系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等, ...

  2. .NET面试题解析(07)-多线程编程与线程同步 (转)

    http://www.cnblogs.com/anding/p/5301754.html 系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实 ...

  3. Win32多线程编程(3) — 线程同步与通信

      一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...

  4. iOS多线程编程:线程同步总结

    1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一 ...

  5. windows lua 多线程 线程同步

    今天在改一个程序,改成部分逻辑用lua写,这个程序是多线程的.将程序中部分逻辑改成lua之后,各种非法访问内存错误,各种奇奇怪怪的问题,不分时间,不分地点的出现崩溃.从调用堆栈来看,基本都是使用lua ...

  6. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  7. java并发编程基础——线程同步

    线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...

  8. Windows编程之线程

    本笔记整理自:<Windows核心编程(第五版)> 目录 何为线程 线程的开始和结束 创建线程 终止线程 线程运行时的调度和线程优先级 挂起(暂停).恢复与睡眠 挂起 恢复 睡眠 线程切换 ...

  9. C# 多线程编程第二步——线程同步与线程安全

    上一篇博客学习了如何简单的使用多线程.其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单.所以很多时候不正确的使用多线程反倒会影响程序的性能. 下面先看一个例子 : class Pro ...

随机推荐

  1. 微服务远程Debug,Nocalhost + Rainbond微服务开发第二弹

    之前的文章中我们介绍了如何通过 Nocalhost 快速开发 Rainbond 上的微服务,介绍了基本的开发流程. 本文将续接上文继续介绍,使用 Nocalhost 开发配置文件 实现以下内容: 一键 ...

  2. Tapdata 在线研讨会:如何快速上手 Tapdata Cloud?

    偶然接触到 Tapdata Cloud,据说不仅可以实现异构数据实时同步,还永久 100% 免费,但就是不知道怎么获取.怎么用? 打开相关文档逐渐陷入迷茫,术语."黑话"随处可见, ...

  3. 洛谷P4017 最大食物链数量 dfs

    老规矩,传送门 做题从头到尾的思路: 1. 这个题明显就是dfs数数量了,简单,邻接矩阵干他! TLE警告,8个点 额... 2. 老师说这玩意不能邻接矩阵?没事,还有邻接表,再来! 再次TLE 8个 ...

  4. idea 内置tomcat jersey 上传文件报403错误

    Request processing failed; nested exception is com.sun.jersey.api.client.UniformInterfaceException: ...

  5. 抢先体验! 在浏览器里写 Flutter 是一种什么体验?

    Invertase 是一间位于英国的开源软件制作公司.主要构建关于开发者工具.SDK 等应用程序,早在 Flutter 2.2 的时候,Invertase 团队就开始帮助构建和贡献 Firebase ...

  6. 01. MySQL__数据库分类和介绍

    数据库的分类 1. 关系型数据库 以表为数据库存储数据的单位,关系型数据库是经过数学理论验证可以保存现实生活中任何关系型的数据 2. 非关系型数据库 主要应用在一些特定场景,解决某些具体问题比如解决高 ...

  7. 【C++】从设计原理来看string类

    1.一些C++基础知识 模板类string的设计属于底层,其中运用到了很多C++的编程技巧,比如模板.迭代器.友元.函数和运算符重载.内联等等,为了便于后续理解string类,这里先对涉及到的概念做个 ...

  8. GitLab:Your account has been blocked.

    使用git pull 出现"GitLab:Your account has been blocked."错误 背景 多人使用服务器同一用户,在~/.ssh 目录下的公私钥是之前一个 ...

  9. python 生成Windows快捷方式

    此处以虚拟机镜像快捷方式为例 link_filepath:快捷方式的生成路径 win32_cmd:需要执行的应用程序 arg_str:exe的参数 快捷方式的目标:由win32_cmd + arg_s ...

  10. 可以级联的以太网远程IO模块的优点与适用场景

    可以级联的以太网远程IO模块的优点与具体的适用场景 对于数据采集控制点是按照线性分布的场景,比如智慧园区的路灯.桥梁.路灯.数字化工厂.停车场车位监测.智慧停车场.智能停车架.楼宇自动控制系统等场景, ...