Windows提高_2.3第三部分:内核区同步

Posted ltyandy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Windows提高_2.3第三部分:内核区同步相关的知识,希望对你有一定的参考价值。

第三部分:内核区同步

等待函数(WaitForObject)

等待函数的形式

  • 单个:WaitForSingleObject

  • 多个:WaitForMultipleObjects

一个可以被等待的对象通常由两种状态,分别是:

  • 可等待(激发态)(有信号):等待函数【不会阻塞】

  • 不可等待(非激发态)(无信号):等待函数需要等待一定时长并【阻塞】

等待函数的副作用:

  • 改变被等待对象的信号状态

#include <iostream>
#include <windows.h>
?
// 工作线程
DWORD WINAPI WorkerThread(LPVOID lpThreadParameter)
{
    // 输出 0 ~ 99
    for (int i = 0; i < 1000; ++i)
    {
        printf("%d\n", i);
    }
?
    return 0;
}
?
int main()
{
    HANDLE Threads[3] = { 0 };
?
    // 创建两个线程
    Threads[0] = CreateThread(NULL, NULL, WorkerThread, NULL, NULL, NULL);
    Threads[1] = CreateThread(NULL, NULL, WorkerThread, NULL, NULL, NULL);
    Threads[2] = CreateThread(NULL, NULL, WorkerThread, NULL, NULL, NULL);
?
    // 函数返回的两种情况
    // 1. 被等待对象处于有信号状态
    // 2. 等到超时函数会返回,但是等待失败
?
    // 等待两个线程退出
    // 1. 一个可以被等待的内核对象
    // 2. 等待时长,如果是-1 表示一直等待
    // WaitForSingleObject(ThreadHandles[0], INFINITE);
    // WaitForSingleObject(ThreadHandles[1], INFINITE);
?
    // 等待多个内核对象
    // 1. 需要等待多少个内核对象
    // 2. 内核对象句柄的数组
    // 3. 是否等待所有内核对象编程变成有信号的
    // 4. 等待的时长,单位是毫秒
    // WaitForMultipleObjects(2, ThreadHandles, TRUE, INFINITE);
?
    // 有任何一个执行完毕,另外一个就不执行了
    WaitForMultipleObjects(3, Threads, FALSE, INFINITE);
?
    // 等待函数的副作用:修改被等待对象的信号状态
?
    return 0;
}

 

互斥体(Mutex)

  • 特点:拥有临界区的线程拥有者概念,但是线程崩溃不会死锁,执行较慢

  • 函数

    • 创建:CreateMutex,如果第二个参数是FALSE,那么就是可等待

    • 打开:OpenMutex

    • 保护:WaitForSingleObject: 把互斥体置为不可等待

    • 释放:ReleaseMutex把互斥体置为可等待

    • 关闭:CloseHandle

// 1. 创建一个互斥体内核对象
// - 安全属性,初始状态,名称
HANDLE Mutex = CreateMutex(NULL, FALSE, L"Mutex");
?
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
    // 为 g_Number 自增 100000 次
    for (int i = 0; i < 100000; i++)
    {
        // 2. 使用等待函数对互斥体进行等待,当互斥体处于激发态就会等待成功
        // 当等待成功时,会将互斥体置为无信号状态
        WaitForSingleObject(Mutex, INFINITE);
        g_Number++;
        // 3. 执行完代码之后需要恢复有信号状态
        ReleaseMutex(Mutex);
    }
    return 0;
}

 

事件(Event)

  • 特点:可以手动选择是否产生副作用,如果设置为手动状态,那么就不产生副作用

  • 函数:

    • 创建:CreateEvent()

    • 打开:OpenEvent()

    • 保护:WaitForSingleObject()

    • 设置为非激发态:ResetEvent()

    • 退出(设置为激发态):SetEvent()

    • 关闭:CloseHandle

// 1. 创建一个事件内核对象
// - 安全属性,是否自动,初始状态,名称
HANDLE Event = CreateEvent(NULL, FALSE, TRUE, L"Event");
// HANDLE Event = CreateEvent(NULL, TRUE, TRUE, L"Event");
?
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
    // 为 g_Number 自增 100000 次
    for (int i = 0; i < 100000; i++)
    {
        // 注意:如果使用手动模式,等待函数就没有副作用
        // 可以使用 ResetEvent 手动设置信号状态,但是没有意义
        // 原因是 ResetEvent 本身不是一个原子操作
?
        // 2. 使用等待函数对事件进行等待,当事件处于激发态就会等待成功
        // 当等待成功时,会将事件置为无信号状态
        WaitForSingleObject(Event, INFINITE);
        g_Number++;
        // 3. 执行完代码之后需要恢复有信号状态
        SetEvent(Event);
    }
    return 0;
}

 

信号量

  • 特点:有多把锁,可以控制活动线程的数量,没有线程拥有者概念。

  • 如果锁的最大个数是1,则用法和互斥体类似

  • 函数:

    • 创建:CreateSemaphore

    • 打开:OpenSemaphore

    • 上锁:WaitForSingleObject

    • 解锁:ReleaseSemaphore

    • 关闭:CloseHandle

// 1. 创建一个信号量内核对象
// - 安全属性,当前信号,最大信号,名称
HANDLE Semaphore = CreateSemaphore(NULL, 1, 1, L"Semaphore");
?
DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter)
{
    // 为 g_Number 自增 100000 次
    for (int i = 0; i < 100000; i++)
    {
        // 2. 使用等待函数对信号量进行等待,当信号个数>1表示可等待
        // 当等待成功时,会将信号个数 -1
        WaitForSingleObject(Semaphore, INFINITE);
        g_Number++;
        // 3. 执行完代码之后需要将信号个数 +1
        // 信号量对象,+几个信号,之前有多少个信号
        ReleaseSemaphore(Semaphore, 1, NULL);
    }
    return 0;
}

 

 

以上是关于Windows提高_2.3第三部分:内核区同步的主要内容,如果未能解决你的问题,请参考以下文章

Windows提高_2.2第二部分:用户区同步

Windows线程同步

Linux内核设计与实现——内核同步

第九章:内核同步介绍

9内核同步介绍

Linux(内核剖析):28---内核同步之(临界区竞争条件同步锁常见的内核并发SMNP和UP配置选项锁的争用和扩展性(锁粒度))