知者何南 发表于 2024-9-2 16:26:44

Windows—线程根本知识和线程同步

线程

线程的组成


[*]线程的内核对象,利用系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
[*]线程堆栈,它用于维护线程在执行代码时必要的所有函数参数和局部变量
线程的进入点

每个线程必须拥有一个进入点函数,线程从这个进入点开始运行。

[*]主线程的进入点函数:即main函数。
[*]假如想要在你的进程中创建一个辅助线程,它肯定也是个进入点函数,雷同下面:
DWORD WINAPI ThreadFunc(PVOID pvParam) {
    DWORD dwResult = 0;
    cout << "hello thank you!" << endl;
    return dwResult;
}
你的线程函数可以执行你想要它做的任何任务。终极,线程函数到达它的末端处并且返回。 这时,线程终止运行,该堆栈的内存被释放,同时,线程的内核对象的利用计数被递减。假如利用计数降为0,线程的内核对象就被撤消 。
利用线程注意事项

:::info

[*]线程函数可以利用任何名字。假如在应用程序中拥有多个线程函数,必须为它们赋予不同的名字。
[*]线程函数必须返回一个值,它将成为该线程的退出代码。
[*]线程函数(实际上是你的所有函数)应该尽大概利用函数参数和局部变量。当利用静态变量和全局变量时,多个线程可以同时访问这些变量,大概破坏变量的内容。参数和局部变量是在线程堆栈中创建的,因此它们不太大概被另一个线程破坏。
:::
CreateThread函数

HANDLE WINAPI CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程内核对象的默认安全属性,传递NULL。
    _In_ SIZE_T dwStackSize,//将多少地址空间用于线程自己的堆栈
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,//线程函数地址,函数名
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,//线程函数的参数
    _In_ DWORD dwCreationFlags,//0创建线程后立即调度
    _Out_opt_ LPDWORD lpThreadId //线程ID
    );

[*]假如要创建一个或多个辅助函数,让一个已经在运行的线程调用CreateThread 。CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身, 是利用系统用来管理线程的较小的数据布局。
[*]系统从进程的所在空间中分配内存,供线程的堆栈利用。新线程运行的进程环境与创建线程的环境相同。新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。
DWORD WINAPI ThreadFunc(PVOID pvParam) {
    DWORD dwResult = 0;
    cout << "hello thank you!" << endl;
    return dwResult;
}
int main(){    CreateThread(NULL,0,ThreadFunc,NULL,0,0);   cout << "Hello World!"<<endl;} https://img-blog.csdnimg.cn/img_convert/cb03a0de88a46fa6c157a116203d2613.png
终止线程的运行

:::info

[*]线程函数返回(最好利用这种方法)。
[*]通过调用ExitThread函数,线程将自行撤消(最好不要利用这种方法)。
[*]同一个进程或另一个进程中的线程调用TerminateThread函数(应该避免利用这种方法)。
[*]包罗线程的进程终止运行(应该避免利用这种方法)。
:::
线程的初始化

https://img-blog.csdnimg.cn/img_convert/1efa2f87b6d364bd27081cd02d361fce.png
:::info

[*]调用CreateThread可使系统创建一个线程内核对 象。该对象的初始利用计数是2(在线程停止运行和从返回的句柄关闭之前,线程内核对象不会被撤消)。线程的内核对象的其他属性也被初始化,暂停计数被设置为1,退出代 码始终为STILL _ ACTIVE(0 x103),该对象设置为未关照状态.。
[*]一旦内核对象创建完成,系统就分配用于线程的堆栈的内存。该内存是从进程的所在空间 分配而来的,由于线程并不拥有它自己的所在空间。然后系统将两个值写入新线程的堆栈的上端(线程堆栈总是从内存的高所在向低所在建立)。
[*]写入堆栈的第一个值是传递给CreateThread 的pvParam参数的值。紧靠它的下面是传递给CreateThread的pfnStartAddr参数的值。
[*]每个线程都有它自己的一组CPU寄存器,称为线程的上下文。该上下文反映了线程前次运 行时该线程的C PU寄存器的状态 。
[*]指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器。线程总是在进程 的上下文中运行的。因此,这些所在都用于标识拥有线程的进程所在空间中的内存。
:::
线程的调度

sleep函数


[*]可使线程自愿放弃它剩余的时间片。
[*]系统将在约莫的指定毫秒数内使线程不可调度。
线程优先级

https://img-blog.csdnimg.cn/img_convert/057264c3303f8b0edd3679c9442b8c60.png
:::info
一般来说,大多数时间高优先级的线程不应该处于可调度状态。当线程要举行某种利用时,它能灵敏获得CPU时间。这时线程应该尽大概少地执行CPU指令,并返回睡眠状态,等待再次变成可调度状态。相反,低优先级的线程可以保持可调度状态, 执行大量的CPU指令来举行它的利用。假如按照这些原则来办,整个利用系统就能精确地对用户作出相应。
:::
进程优先级

https://img-blog.csdnimg.cn/img_convert/0a8684cc3cc5fbe0f995f49c7ab84ba5.png
线程同步

什么时间必要线程同步


[*]当有多个线程访问共享资源而不使资源被破坏时。
[*]当一个线程必要将某个任务已经完成的环境关照另外一个或多个线程时。
什么是句柄hHandle


[*]Windows之以是要设立句柄,根本上源于内存管理机制的题目,即虚拟所在。数据的所在必要变更,变更以后就必要有人来记载、管理变更,因此系统用句柄来纪录数据所在的变更。
[*]句柄是一种特殊的智能指针,当一个应用程序要引用其他系统(如数据库、利用系统)所管理的内存块或对象时,就要利用句柄。
[*]句柄一般是指获取另一个对象的方法—一个广义的指针,它的详细形式大概是一个整数、一个对象或就是一个真实的指针,目的就是建立起与被访问对象之间的唯一的接洽 。在C++中,要访问一个对象,通常可以建立一个指向对象的指针。但是在很多详细的应用中,直接用指针代表对象并不是一个好的解决方案。
[*]是一个用来标识对象大概项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量 。句柄是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控件、位图、GDI对象、资源、文件等。
[*]从数据类型上来看它只是一个16位的无符号整数。应用程序总是通过调用Windows API获得一个句柄,之后其他 Windows函数就可以利用该句柄,以引用和利用相应的内核对象。句柄可以像指针那样置空,那样句柄就没有任何意义,不代表任何内核对象。
[*]在 Windows程序中并不是用物理所在来标识一个内存块、文件、任务或动态装入模块的。Windows API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来举行利用
线程同步的方式

:::info

[*]原子利用
[*]临界区
[*]互斥锁
[*]信号量
[*]条件变量
[*]事件
[*]读写锁
[*]自旋锁
[*]共享内存
[*]屏障
:::
实例

不同步例子

输出居然x=1
int x = 0;

DWORD WINAPI FirstThread(PVOID pvParam) {
        x++;
        return 0;
}
       
DWORD WINAPI SecondThread(PVOID pvParam) {
        x++;
        return 0;
}
       
int main(){
        //主线程,线程1,线程2谁先运行完不知道
        CreateThread(NULL,0, FirstThread,NULL,0,0);
        CreateThread(NULL, 0, SecondThread, NULL, 0, 0);
          
        cout << "x="<<x<<endl;
        return 0;
}

原子利用

原子访问:指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源。
long x = 0;

DWORD WINAPI FirstThread(PVOID pvParam) {
        InterlockedExchangeAdd(&x, 1);
        return 0;
}
       
DWORD WINAPI SecondThread(PVOID pvParam) {
        InterlockedExchangeAdd(&x, 1);
        return 0;
}
       
int main(){
       
        CreateThread(NULL,0, FirstThread,NULL,0,0);
        CreateThread(NULL, 0, SecondThread, NULL, 0, 0);

        Sleep(1);//主线程等等子线程
          
        cout << "x="<<x<<endl;
        return 0;
}
临界区

:::info
包管在某一时间只有一个线程能访问数据的轻巧办法。在恣意时间只允许一个线程对共享资源举行访问。假如有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继承抢占,以此到达用原子方式利用共享资源的目的。
:::
临界区和互斥量的区别

:::info
1、临界区只能用于对象在同一进程里线程间的互斥访问;互斥体可以用于对象进程间或线程间的互斥访问。
2、临界区是非内核对象,只在用户态举行锁利用,速度快;互斥体是内核对象,在焦点态举行锁利用,速度慢。
3、临界区和互斥体在Windows平台下都可用;Linux下只有互斥体可用。
4、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,得当控制数据访问。
5、互斥量:为协调共同对一个共享资源的单独访问而计划的。
:::
临界区的利用步调

:::info
1 . 申请一个临界区变量 CRITICAL_SECTION gSection;
2. 初始化临界区 InitializeCriticalSection(&gSection);
3. 利用临界区 EnterCriticalSection(&gSection); …省略代码…LeaveCriticalSection(&gSection);
4.释放临界区 DeleteCriticalSection(&gSection);
:::
形象化解释

:::info

[*]临界区就像飞机上的茅厕,抽水马桶是要保护的数据。茅厕很小, 每次只能一个人(线程)进入茅厕(关键代码段)利用抽水马桶(受保护的资源)。 假如有多个资源被一道利用,可以将它们全部放在一个茅厕里,也就是说可以创建一 个CRITICAL_SECTION布局来保护所有的资源 。
[*]假如有多个不是一道利用的资源,好比线程1和线程2访问一个资源,而线程1和线程3访问 另一个资源,那么应该为每个资源创建一个独立的茅厕,即CRITICAL_SECTION布局。
[*]无论在何处拥有必要访问资源的代码,都必须调用CRITICAL_SECTION函数,为它传递用于标识该资源的CRITICAL_SECTION布局的所在。当一个线程必要访问一 个资源时,它首先必须检查茅厕门上的“有人”标记。CRITICAL_SECTION布局用于标识线 程想要进入哪个茅厕,而EnterCriticalSection函数则是线程用来检查“有人”标记的函数 。
[*]假如EnterCriticalSection函数发现茅厕中没有任何别的线程(门上的标记表现“无人”), 那么调用线程就可以利用该资源。假如EnterCriticalSection发现茅厕中有另一个线程正在利用, 那么调用函数必须在茅厕门的表面等待,直到茅厕中的另一个线程离开茅厕。
[*]当一个线程不再执行必要访问资源的代码时,它应该调用LeaveCriti calSection函数。告诉系统它准备离开包罗该资源的茅厕。假如忘记调用LeaveCriti calSection,系统将认 为该线程仍然在茅厕中,因此不允许其他正在等待的线程进入茅厕。这就像离开了茅厕但没有 换上“无人”的标记 。
:::
long x = 0;

CRITICAL_SECTION cs;

DWORD WINAPI FirstThread(PVOID pvParam) {
        EnterCriticalSection(&cs);
        x++;
        LeaveCriticalSection(&cs);
        return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
        EnterCriticalSection(&cs);
        x++;
        LeaveCriticalSection(&cs);
        return 0;
}

int main() {
        //初始化临界区
        InitializeCriticalSection(&cs);
        //创建线程,绑定入口函数
        HANDLE handle1=CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
        HANDLE handle2=CreateThread(NULL, 0, SecondThread, NULL, 0, 0);
        //主线程等待子线程结束,就不用特意Sleep了
        WaitForSingleObject(handle1, INFINITE);
        WaitForSingleObject(handle2, INFINITE);
        //关闭线程句柄
        CloseHandle(handle1);
        CloseHandle(handle2);

    //销毁
        DeleteCriticalSection(&cs);

        cout << "x=" << x << endl;
        return 0;
}

临界区的长处

:::info

[*]描述: 临界区是Windows系统中的轻量级同步对象,允许多个线程互斥访问共享资源。与互斥锁雷同,但性能较好,实用于单进程中的线程同步。
[*]长处: 比互斥锁更快,不涉及内核对象的开销。
[*]缺点: 只能用于同一进程内的线程同步,不能跨进程利用
:::
不关闭线程句柄的影响

:::info
假如不关闭线程句柄,大概会导致系统资源泄漏,特殊是在创建大量线程的环境下。这是由于每个线程句柄占用系统资源,并且在不再必要该线程时,应该通过CloseHandle来释放这些资源。假如你不关闭线程句柄,资源会一直占用,直到你的进程结束。
:::
互斥锁

:::info

[*]描述: 互斥锁是一种保护共享资源的同步原语。在同一时间,只允许一个线程持有互斥锁,从而包管对共享资源的互斥访问。
[*]长处: 简朴易用,得当对资源举行独占式访问的场景。
[*]缺点: 大概导致死锁(假如多个线程相互等待对方释放锁)
:::
#include<mutex>
long x = 0;
mutex mx;

DWORD WINAPI FirstThread(PVOID pvParam) {
        mx.lock();
        x++;
        mx.unlock();
        return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
        mx.lock();
        x++;
        mx.unlock();
        return 0;
}

int main() {
        //创建线程,绑定入口函数
        HANDLE handle1=CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
        HANDLE handle2=CreateThread(NULL, 0, SecondThread, NULL, 0, 0);
        //主线程等待子线程结束
        WaitForSingleObject(handle1, INFINITE);
        WaitForSingleObject(handle2, INFINITE);
        //关闭线程句柄
        CloseHandle(handle1);
        CloseHandle(handle2);

        cout << "x=" << x << endl;
        return 0;
}

信号量

:::info

[*]描述: 信号量允许多个线程访问共享资源,可以控制访问资源的线程数目。计数信号量用于控制同时访问特定资源的线程数量,而二进制信号量相称于一个可以在两种状态之间切换的锁。
[*]长处: 可以限定资源的并发访问,得当用于限流或生产者-消费者模式。
[*]缺点: 比互斥锁复杂,必要警惕管理信号量的计数。
:::
#include <iostream>
#include <windows.h>

using namespace std;

long x = 0;
HANDLE semaphore;

DWORD WINAPI FirstThread(PVOID pvParam) {
    WaitForSingleObject(semaphore, INFINITE);// 等待信号量,进入临界区
    x++;// 修改共享资源
    ReleaseSemaphore(semaphore, 1, NULL);// 释放信号量,离开临界区
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    WaitForSingleObject(semaphore, INFINITE);// 等待信号量,进入临界区
    x++;// 修改共享资源
    ReleaseSemaphore(semaphore, 1, NULL);// 释放信号量,离开临界区
    return 0;
}

int main() {
    semaphore = CreateSemaphore(NULL, 1, 1, NULL);// 创建信号量,初始值为1,最大值为1

    // 创建线程,绑定入口函数
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, 0);

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    // 关闭信号量句柄
    CloseHandle(semaphore);

    cout << "x = " << x << endl;
    return 0;
}
:::info


[*]CreateSemaphore: 初始化信号量,初始值和最大值都为1,这使得信号量在同一时间只允许一个线程进入临界区,起到了雷同互斥锁的作用。
[*]WaitForSingleObject: 等待信号量,信号量计数减1,假如当前值为0则线程阻塞,直到其他线程释放信号量。
[*]ReleaseSemaphore: 释放信号量,信号量计数加1,允许其他等待的线程进入临界区。
[*]CloseHandle: 在利用完信号量后,关闭信号量句柄,释放资源。
:::
CreateSemaphore


[*]lInitialCount:信号量的初始计数值,表现信号量的可用资源数量。当该值为0时,任何调用WaitForSingleObject的线程都会立即被阻塞,直到其他线程调用ReleaseSemaphore。
[*]lMaximumCount:信号量计数的最大值,表现信号量允许的最大资源数量。ReleaseSemaphore调用时,计数值不能超过这个值,否则函数调用失败。
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性 (可选)
LONG lInitialCount,                        // 初始计数
LONG lMaximumCount,                        // 最大计数
LPCWSTR lpName                               // 信号量名称 (可选)
);

ReleaseSemaphore

lReleaseCount: 释放的计数值,即增加信号量计数器的值。此值必须大于0,表现你盼望释放的资源数量。比方,假如设置为1,信号量计数增加1,释放一个资源 。
BOOL ReleaseSemaphore(
HANDLE hSemaphore,      // 信号量句柄
LONG lReleaseCount,       // 释放的计数
LPLONG lpPreviousCount    // 上一个计数 (可选)
);
条件变量

:::info

[*]描述: 条件变量允许线程在某个条件不满足时等待,条件满足时关照其他线程继承执行。条件变量通常与互斥锁配合利用。
[*]长处: 得当用于必要等待某个条件发生的场景,如线程间的复杂协调。
[*]缺点: 利用复杂度较高,尤其是在处置惩罚多个条件时。
:::
#include <iostream>
#include <windows.h>
#include <condition_variable>
#include <mutex>

using namespace std;

long x = 0;
mutex mtx;
condition_variable cv;
bool ready = true;// 初始状态为true, 第一个线程可以进入临界区

DWORD WINAPI FirstThread(PVOID pvParam) {
    unique_lock<mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });// 等待条件变量,条件满足时继续执行
    ready = false;// 进入临界区后将条件设为false,阻止其他线程进入
    x++;// 修改共享资源
    ready = true;// 修改完共享资源后恢复条件,允许其他线程进入
    lock.unlock();// 解锁互斥锁
    cv.notify_one();// 通知其他等待线程
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    unique_lock<mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });// 等待条件变量,条件满足时继续执行
    ready = false;// 进入临界区后将条件设为false,阻止其他线程进入
    x++;// 修改共享资源
    ready = true;// 修改完共享资源后恢复条件,允许其他线程进入
    lock.unlock();// 解锁互斥锁
    cv.notify_one();// 通知其他等待线程
    return 0;
}

int main() {
    // 创建线程,绑定入口函数
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, 0);

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    cout << "x = " << x << endl;
    return 0;
}
事件

:::info

[*]描述: 事件是一种同步机制,可以在一个线程中设置事件状态,另一个线程中等待事件的触发。事件有自动重置和手动重置两种类型。
[*]长处: 可以在线程间发出信号关照,非常得当用于一个线程关照多个线程的场景。
[*]缺点: 必要额外的资源管理。
:::
#include <iostream>
#include <windows.h>

using namespace std;

long x = 0;
HANDLE eventHandle;

DWORD WINAPI FirstThread(PVOID pvParam) {
    WaitForSingleObject(eventHandle, INFINITE);// 等待事件变为有信号状态
    x++;// 修改共享资源
    SetEvent(eventHandle);// 设置事件为有信号状态,允许其他线程继续执行
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    WaitForSingleObject(eventHandle, INFINITE);// 等待事件变为有信号状态
    x++;// 修改共享资源
    SetEvent(eventHandle);// 设置事件为有信号状态,允许其他线程继续执行
    return 0;
}

int main() {
    // 创建自动重置事件对象,初始状态为有信号状态
    eventHandle = CreateEvent(NULL, FALSE, TRUE, NULL);

    // 创建线程,绑定入口函数
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, 0);

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    // 关闭事件句柄
    CloseHandle(eventHandle);

    cout << "x = " << x << endl;
    return 0;
}

:::info
事件在某个线程调用WaitForSingleObject并成功进入临界区后,自动重置为无信号状态,制止其他线程进入临界区,确保线程间的互斥访问
:::
CreateEvent

/*
* 功能:创建一个事件对象。(有人说创建或打开一个命名的或无名的事件对象,
当名字为参4时,会返回已打开的事件对象,
但是我下面的案例测试是无法根据参4(无论是否为NULL都不行)获取已打开的对象)。
* 返回值:返回一个句柄HANDLE。
* 参1:属性,一般传NULL即可。
* 参2:是否设置手动改变事件状态。false自动,true手动。
* 参3:状态的初始值,分为无状态和有状态,false代表无状态,true代表有状态。
* 参4:事件的名字,可以为NULL。
*/
CreateEventW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_ BOOL bManualReset,
    _In_ BOOL bInitialState,
    _In_opt_ LPCWSTR lpName
);
SetEvent

/*
* 功能:设置状态为有状态。
* 返回值:1成功,0失败,该返回值实际意义不大。
* 参1:一个内核对象的句柄,不过主要是Event。
*/
SetEvent(
    _In_ HANDLE hEvent
);
ResetEvent

/*
* 功能:设置状态为无状态。
* 返回值:1成功,0失败,该返回值实际意义不大。
* 参1:一个内核对象的句柄,不过主要是Event。
*/
ResetEvent(
    _In_ HANDLE hEvent
);
WaitForSingleObject

/*
* 功能:阻塞等待状态改变返回。
* 返回值:返回DWORD的值,一般使用宏去判断,若立即返回,返回值为WAIT_OBJECT_0;
          超时返回WAIT_TIMEOUT;失败返回WAIT_FAILED。
* 参1:一个内核对象的句柄,可以是Event,Mutex,Semaphore(信号量),Process,Thread。
* 参2:等待时长,单位ms。
*
* 注意:参2的取值:
* 1)传0:表示不阻塞,立即返回,返回值为WAIT_OBJECT_0。
* 2)传>0:阻塞时长,超时时返回WAIT_TIMEOUT。
* 3)传INFINITE:表示一直阻塞,直到等待句柄的状态发生改变。
*/
WaitForSingleObject(
    _In_ HANDLE hHandle,
    _In_ DWORD dwMilliseconds
);
案例

:::info
需求:主线程通过叫醒线程2退却出while循环,线程2等到event2后叫醒线程1,线程1等待event1,线程1是手动改变状态,调用完WaitForSingleObject是无法自动改变状态为无状态,以是最退却出时必须手动调用ResetEvent将状态改变为无状态。最后主线程由于两个线程都退出后,主线程就会退出循环,并且接纳句柄。注意主线程阻塞等待的是两个线程,而两个线程等待的是事件。
:::
#include <iostream>
#include <string>
#include <windows.h>
#include <tchar.h>
using namespace std;

HANDLE h_event1 = NULL;
HANDLE h_event2 = NULL;
DWORDFunProc1(LPVOID lpParameter);
DWORDFunProc2(LPVOID lpParameter);

//h_event1初始状态为无信号时,WaitForSingleObject(h_event1, 300)
DWORDFunProc1(LPVOID lpParameter)
{
        cout << "线程1开始运行。\n" << endl;
        while (1)
        {
                int ret = WaitForSingleObject(h_event1, 7000);
                if (WAIT_OBJECT_0 == ret) {
                        cout << "线程1等到event1\n" << endl;
                        break;
                }
                else if (WAIT_TIMEOUT == ret) {
                        cout << "线程1等待event1超时\n" << endl;
                }
                else if (WAIT_FAILED == ret) {
                        cout << "线程1调用WaitForSingleObject失败\n" << endl;
                }
                else {
                        cout << "线程1调用WaitForSingleObject返回未知结果\n" << endl;
                        break;
                }

        }

        cout << "线程1等到了event1,线程1结束。\n" << endl;
        ResetEvent(h_event1);//因为创建事件时信号改变设置为手动改变,所以必须自动调用改变为无信号
        return 0;
}

DWORD FunProc2(LPVOID lpParameter)
{
        cout << "线程2开始运行。\n" << endl;
        while (1) {
                int ret = WaitForSingleObject(h_event2, 3000);//因为创建事件设置为自动,收到信号不阻塞后,该函数返回自动将状态改为无信号状态
                if (WAIT_OBJECT_0 == ret) {
                        cout << "线程2等到event2\n" << endl;
                        break;
                }
                else if (WAIT_TIMEOUT == ret) {
                        cout << "线程2等待event2超时\n" << endl;
                }
                else if (WAIT_FAILED == ret) {
                        cout << "线程2调用WaitForSingleObject失败\n" << endl;
                }
                else {
                        cout << "线程2调用WaitForSingleObject返回未知结果\n" << endl;
                        break;
                }

        }
        cout << "线程2等到了event2,线程2结束,并唤醒线程1。\n" << endl;
        Sleep(350);
        SetEvent(h_event1);

        return 0;
}

int main(int argc, char** argv)
{
        h_event1 = CreateEvent(NULL, true, false, _T("event_one"));//参2代表设置手动改变状态,参3代表初始状态为无状态
        h_event2 = CreateEvent(NULL, false, false, _T("event_two"));//参2代表设置自动改变状态

        HANDLE hThread1;
        hThread1 = CreateThread(NULL, 0, FunProc1, NULL, 0, NULL);
        HANDLE hThread2;
        hThread2 = CreateThread(NULL, 0, FunProc2, NULL, 0, NULL);

        Sleep(5000);
        SetEvent(h_event2);

        //线程1或者线程2都没退出继续等待,注意每个线程阻塞过程中收到信号改变立马不阻塞,并且结束的线程下次调用WaitForSingleObject直接返回
        while (WaitForSingleObject(hThread1, 150) != WAIT_OBJECT_0 || WaitForSingleObject(hThread2, 150) != WAIT_OBJECT_0)
        {
                cout << "线程还没有结束,主程序等了150ms。\n" << endl;
        }

        cout << "主线程等待两个子线程结束完毕" << endl;
        CloseHandle(hThread1);
        CloseHandle(hThread2);
        CloseHandle(h_event1);
        CloseHandle(h_event2);

        system("pause");
        return 0;
}
//改成INFINITE就是一直阻塞
WaitForMultiObject

/*
* 功能:同样是阻塞等待状态改变返回。
* 返回值:返回DWORD的值,一般使用宏去判断,若立即返回,返回值为WAIT_OBJECT_0;
          超时返回WAIT_TIMEOUT;失败返回WAIT_FAILED。
* 参1:句柄的数量,最大值为MAXIMUM_WAIT_OBJECTS(64),
      可以是Event,Mutex,Semaphore(信号量),Process,Thread。
* 参2:句柄数组的指针。
* 参3:等待的类型,如果为TRUE 则等待所有信号量有效再往下执行,
      FALSE 当有其中一个信号量有效时就向下执行。
* 参4:等待时长,单位ms。
* 注意:参4的取值:
* 1)传0:表示不阻塞,立即返回,返回值为WAIT_OBJECT_0。
* 2)传>0:阻塞时长,超时时返回WAIT_TIMEOUT。
* 3)传INFINITE:表示一直阻塞,直到等待句柄的状态发生改变。
*/
WaitForMultipleObjects(
    _In_ DWORD nCount,
    _In_reads_(nCount) CONST HANDLE* lpHandles,
    _In_ BOOL bWaitAll,
    _In_ DWORD dwMilliseconds
);
案例

m_threadShow = std::thread(std::mem_fn(&MainWindow::ShowData), this);

MainWindow::~MainWindow()
{
    SetEvent(m_KillEvent);
    if(m_threadShow.joinable())
      m_threadShow.join();
    delete ui;
}

void MainWindow::ShowData()
{
    while(1)
    {
      HANDLE sigs = {m_KillEvent, m_showEvent};
              const DWORD ret = WaitForMultipleObjects(2, sigs, false, dwMilliseconds);
                switch (ret) {
                case WAIT_OBJECT_0: {
                        //数组的第一个事件:m_KillEvent发生会来到这里
                        break;
                }

                case WAIT_OBJECT_0 + 1: {
                        //数组的第二个事件:m_showEvent发生会来到这里
                        break;
                }
                case WAIT_TIMEOUT: {
                        // 超时来到这里
                }
                default: {
                        // 其它未知返回值的处理
                }
    }
}
读写锁

:::info


[*]描述: 读写锁允许多个线程同时读取共享资源,但在写入时,只有一个线程可以独占访问。读锁和写锁是分开的。
[*]长处: 提高了读利用的并发性,得当读多写少的场景。
[*]缺点: 假如写利用过多,大概会导致读利用被阻塞
:::
#include <iostream>
#include <windows.h>

using namespace std;

long x = 0;
SRWLOCK rwLock;

DWORD WINAPI FirstThread(PVOID pvParam) {
    AcquireSRWLockExclusive(&rwLock);// 获取写锁,进入临界区
    x++;// 修改共享资源
    ReleaseSRWLockExclusive(&rwLock);// 释放写锁,离开临界区
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    AcquireSRWLockExclusive(&rwLock);// 获取写锁,进入临界区
    x++;// 修改共享资源
    ReleaseSRWLockExclusive(&rwLock);// 释放写锁,离开临界区
    return 0;
}

int main() {
    InitializeSRWLock(&rwLock);// 初始化读写锁

    // 创建线程,绑定入口函数
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, 0);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, 0);

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    cout << "x = " << x << endl;
    return 0;
}

自旋锁

:::info


[*]描述: 自旋锁是一种忙等待的锁,线程在实验获取锁时会不断循环检查锁的状态,直到成功获取锁。得当在锁争用时间很短的场景。
[*]长处: 无需线程切换,实用于短期锁定的场景。
[*]缺点: 大概浪费CPU时间,容易导致忙等题目
:::
#include <iostream>
#include <atomic>
#include <windows.h>

using namespace std;

long x = 0;
std::atomic_flag lock = ATOMIC_FLAG_INIT;// 初始化自旋锁

void AcquireSpinLock() {
    // 自旋直到获取锁
    while (lock.test_and_set(std::memory_order_acquire)) {
      // 可以插入短暂的暂停来降低CPU占用率
      Sleep(0);// 暂停一段时间,让其他线程有机会运行
    }
}

void ReleaseSpinLock() {
    lock.clear(std::memory_order_release);// 释放锁
}

DWORD WINAPI FirstThread(PVOID pvParam) {
    AcquireSpinLock();// 获取自旋锁
    x++;// 修改共享资源
    ReleaseSpinLock();// 释放自旋锁
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    AcquireSpinLock();// 获取自旋锁
    x++;// 修改共享资源
    ReleaseSpinLock();// 释放自旋锁
    return 0;
}

int main() {
    // 创建线程,绑定入口函数
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, NULL);

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    cout << "x = " << x << endl;
    return 0;
}
:::info
Sleep(0) 的作用:


[*]低落 CPU 占用: 自旋锁的忙等循环会占用大量的 CPU 资源,而 Sleep(0) 使线程暂时放弃 CPU 资源,淘汰无用的忙等,允许其他线程在同一时间片内执行,从而提高整体系统的相应性。
[*]线程调度: Sleep(0) 给利用系统提供了一个时机来调度其他线程。如许,纵然自旋锁的持有者线程由于某些缘故原由没有释放锁,等待的线程也不会完全浪费 CPU 资源在忙等上。
:::
自旋锁和互斥锁的区别

:::info

[*]自旋锁:
长处: 在锁竞争不激烈和临界区很短的环境下,开销小,性能高。
缺点: 假如锁竞争激烈或持有时间长,会导致高 CPU 占用和性能下降。

[*]互斥锁:
长处: 在锁竞争高和临界区较长的环境下表现良好,能够有效地处置惩罚线程阻塞和上下文切换。
缺点: 大概引入额外的上下文切换开销,在锁竞争低的环境下性能较差
:::
共享内存

#include <iostream>
#include <windows.h>

using namespace std;

const int BUFFER_SIZE = sizeof(long);// 共享内存的大小
long* sharedMemory;// 指向共享内存区域的指针
HANDLE hMapFile;   // 文件映射对象的句柄
HANDLE hMutex;       // 互斥锁的句柄

DWORD WINAPI FirstThread(PVOID pvParam) {
    WaitForSingleObject(hMutex, INFINITE);// 获取互斥锁
    (*sharedMemory)++;// 修改共享资源
    ReleaseMutex(hMutex);// 释放互斥锁
    return 0;
}

DWORD WINAPI SecondThread(PVOID pvParam) {
    WaitForSingleObject(hMutex, INFINITE);// 获取互斥锁
    (*sharedMemory)++;// 修改共享资源
    ReleaseMutex(hMutex);// 释放互斥锁
    return 0;
}

int main() {
    // 创建文件映射对象
    hMapFile = CreateFileMapping(
      INVALID_HANDLE_VALUE,    // 使用系统分页文件
      NULL,                  // 默认安全属性
      PAGE_READWRITE,          // 可读写
      0,                     // 最大对象大小(高 32 位)
      BUFFER_SIZE,             // 最大对象大小(低 32 位)
      TEXT("SharedMemoryName") // 名称
    );

    if (hMapFile == NULL) {
      cerr << "Could not create file mapping object. Error code: " << GetLastError() << endl;
      return 1;
    }

    // 映射视图到进程地址空间
    sharedMemory = (long*) MapViewOfFile(
      hMapFile,               // 文件映射对象的句柄
      FILE_MAP_ALL_ACCESS,      // 可读写权限
      0,                        // 文件映射偏移(高 32 位)
      0,                        // 文件映射偏移(低 32 位)
      BUFFER_SIZE               // 映射的大小
    );

    if (sharedMemory == NULL) {
      cerr << "Could not map view of file. Error code: " << GetLastError() << endl;
      CloseHandle(hMapFile);
      return 1;
    }

    // 创建互斥锁
    hMutex = CreateMutex(
      NULL,            // 默认安全属性
      FALSE,         // 不拥有初始状态
      TEXT("Global\\MyMutex") // 名称
    );

    if (hMutex == NULL) {
      cerr << "Could not create mutex. Error code: " << GetLastError() << endl;
      UnmapViewOfFile(sharedMemory);
      CloseHandle(hMapFile);
      return 1;
    }

    // 初始化共享内存区域
    *sharedMemory = 0;

    // 创建线程
    HANDLE handle1 = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);
    HANDLE handle2 = CreateThread(NULL, 0, SecondThread, NULL, 0, NULL);

    if (handle1 == NULL || handle2 == NULL) {
      cerr << "Could not create threads. Error code: " << GetLastError() << endl;
      CloseHandle(hMutex);
      UnmapViewOfFile(sharedMemory);
      CloseHandle(hMapFile);
      return 1;
    }

    // 主线程等待子线程结束
    WaitForSingleObject(handle1, INFINITE);
    WaitForSingleObject(handle2, INFINITE);

    // 关闭线程句柄
    CloseHandle(handle1);
    CloseHandle(handle2);

    // 输出结果
    cout << "x = " << *sharedMemory << endl;

    // 清理资源
    CloseHandle(hMutex);
    UnmapViewOfFile(sharedMemory);
    CloseHandle(hMapFile);

    return 0;
}
屏障

:::info


[*]描述: 屏障允许多个线程在到达一个同步点之前阻塞,直到所有线程都到达这个点时,才气继承执行。实用于并行计算中的阶段性同步。
[*]长处: 可以强制多个线程在同一时间到达同步点,实用于阶段性任务。
[*]缺点: 大概会导致线程在等待期间浪费资源。
:::
#include <iostream>
#include <windows.h>

using namespace std;

int x = 0;
HANDLE hBarrierEvent; // 屏障事件句柄

DWORD WINAPI FirstThread(PVOID pvParam) {
    x++;
    SetEvent(hBarrierEvent); // 线程1完成工作,通知主线程
    return 0;
}
       
DWORD WINAPI SecondThread(PVOID pvParam) {
    x++;
    SetEvent(hBarrierEvent); // 线程2完成工作,通知主线程
    return 0;
}
       
int main(){
    // 创建一个事件用于线程同步
    hBarrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
   
    // 主线程,线程1,线程2谁先运行完不知道
    HANDLE hThread1 = CreateThread(NULL, 0, FirstThread, NULL, 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, SecondThread, NULL, 0, NULL);

    // 等待两个线程完成
    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);
   
    // 等待屏障事件被设置
    WaitForSingleObject(hBarrierEvent, INFINITE);

    cout << "x=" << x << endl;

    // 释放资源
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    CloseHandle(hBarrierEvent);

    return 0;
}

内核对象同步


[*]当进程正在运行的时间,进程内核对象处于未关照状态,当进程终止运行的时间,它就变 为已关照状态。进程内核对象中是个布尔值,当对象创建时,该值被初始化为FALSE(未关照状态)。当进程终止运行时,利用系统自动将对应的对象布尔值改为TRUE,表现该对象已经得到关照
[*]线程可以使自己进入等待状态,直到一个对象变为已关照状态。注意,用于控制每个对象 的已关照/未关照状态的规则要根据对象的类型而定。前面已经提到进程和线程对象的规则及作业的规则。
https://img-blog.csdnimg.cn/img_convert/9fbb8484c8399b90145f4867a3a50f17.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Windows—线程根本知识和线程同步