线程同步

设计原型

  1. 我们先设计一个线程制造器

    1. 负责 创建所有线程
    2. 等待 所有线程结束
  2. 在所有 子线程中 开始时 就 锁定代码块/等待开始信号
  3. 在所有 子线程中 结束时 就 解锁代码块/释放开始信号
  4. 参考源码

原子函数

  1. 对数进行原子操作
  2. 防止 一个变量被两个线程复写
InterlockedExchange
InterlockedDecrement
InterlockedIncrement
ExInterlockedAddLargeInteger
ExInterlockedAddUlong

举例

  1. 如果一个变量 Long value =0;
  2. 首先说一下正常情况下的加减操作:value+=1;
  3. 系统从Value的空间取出值,并动态生成一个空间来存储取出来的值;
  4. 将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束。
  5. 如果此时有两个Thread ,分别记作threadA,threadB。
  6. threadA将Value从存储空间取出,为0;
  7. threadB将Value从存储空间取出,为0;
  8. threadA将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束,Value=1。
  9. threadB将取出来的值和1作加法,并且将和放回Value的空间覆盖掉原值。加法结束,Value=1。
  10. 最后Value =1 ,而正确应该是2;
  11. 这就是问题的所在,InterLockedIncrement 能够保证在一个线程访问变量时其它线程不能访问。

临界区

  1. 临界区是最简单的一种锁
  2. 只能在当前进程中操作
  3. 共享一个锁代码块不能同时运行,会等待其代码块结束
CRITICAL_SECTION cs;
InitializeCriticalSection
EnterCriticalSection
LeaveCriticalSection
DeleteCriticalSection

//初始化
CRITICAL_SECTION cs;
//配置环境,必须在线程制造者中
InitializeCriticalSection

//锁定代码块
EnterCriticalSection(/*...*/)

do_something();

//释放代码块
LeaveCriticalSection(/*...*/)

互斥锁

  1. 和临界区 一样都是 限制代码块
  2. 和临界区不同的是,它可以被不同进程使用,因为它有名字
  3. 获取锁和释放锁的线程必须是同一个线程
  4. 由操作系统收发信号,通过0环交互, 效率较低
CreateMutex
OpenMutex
WaitForMultipleObjects
ReleaseMutex
CloseHandle

//主线程,开启所有子线程,并且等待所有线程结束
/*
如果希望线程立即开始 那么就 CreateMutex(,FALSE,)

如果 CreateMutex(,TRUE,),那么 必须等到主线程中 ReleaseMutex ,子线程才会开始
*/
CreateMutex(,FALSE,)
WaitForMultipleObjects
CloseHandle

//子线程
WaitForSingleObject(/*...*/); //等待 互斥锁 释放,并且吃掉这个信号
do_something();
ReleaseMutex(/*...*/);//释放 互斥锁句柄

信号量/线程池

  1. 信号量互斥锁 基本相同
  2. 不过信号量 的信号数量是可以自定义的
  3. 互斥锁 只允许一个 锁定的代码块运行
  4. 信号量 允许多个 锁定的代码块并行运行
  5. 可以理解为 停车场停车事件
  6. 效率较低
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTESlpSemaphoreAttributes, // 安全设置,可为NULL
LONGlInitialCount, // 初始可用车位
LONGlMaximumCount, // 最大并行数量
LPCTSTRlpName// 信号灯名字
);

CreateSemaphore
OpenSemaphore
ReleaseSemaphore//可以 释放多个车位

  //信号量的使用和互斥锁差不多。关键是信号量在初始化的时候需要明确当前资源的数量和信号量的初始状态是什么,

WaitForSingleObject(/*...*/);
    do_something();
ReleaseSemaphore(/*...*/);

实例

#include "pch.h"
#include "windows.h"
#include <iostream>
#include "tlhelp32.h"

DWORD ncount = 0;
HANDLE hd_th_pool = NULL;

DWORD WINAPI ThreadProc(LPVOID lpParameter)   // thread data);
{
    //等待 获取一个 车位名额
    WaitForSingleObject(hd_th_pool, INFINITE);

    //停车+存放
    DWORD tid = InterlockedIncrement(&ncount);//原子加
    printf("我是线程 %d\r\n",ncount);
    Sleep(1000);

    //开出停车场,并且 释放一个车位
    ReleaseSemaphore(hd_th_pool,1,0);

    return 0;
}

int main()
{
    /*
    创建10个车位(车位多少其实无关紧要)
    目前只有2个车位可以用(相当于只允许 2个任务并行)

    //创建一个无限大停车场,但是只允2 辆车
    CreateSemaphoreA(NULL, 2, INFINITE, "my_th_pool");

    */
    hd_th_pool = CreateSemaphoreA(NULL, 2, 10, "my_th_pool");



    /*
    来了20辆车
    开始排队
    */
    for (int i = 0; i < 20; i++) {
        DWORD dwThreadID = 0;

        CreateThread(NULL,
            0,
            (LPTHREAD_START_ROUTINE)ThreadProc,
            NULL,
            0,
            &dwThreadID);

    }
    
    system("pause");
}   

event

  1. 通过 等待事件 来防止多线程复合运行
  2. 通过内核 发送事件
  3. 可以被不同进程使用
  4. 涉及到0环与3环的操作
  5. 效率比较低
CreateEvent
OpenEvent
PulseEvent
ResetEvent
SetEvent

CreateEvent(/*...*/); //创建新号
SetEvent(/*...*/);    //发送信号
WaitForMultiObjects(hThread, /*...*/);  //等待信号
CloseHandle(/*...*/);



Last modification:October 26, 2018
如果觉得我的文章对你有用,请随意赞赏