线程同步的八种利用方法小结

分享者才是学习中最大的受益者!!

线程同步

前言

线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态

简单来说,就是线程之间的协调,线程之间的协同、协助、互相配合完成工作。

一、等待函数

解决的问题

问题一:不同线程函数的执行必须有先后顺序

问题二:同一线程函数中一段代码必须作为一个单元执行

简单的案例

image-20211125203046330

等待函数

DWORD WaitForSingleObject( 
    HANDLE hHandle, //对象句柄 
    DWORD dwMilliseconds //等待时间,毫秒为单位,INFINITE一直等待 
);

hHandle:Event、Job、Memory resource notification、Mutex、Process、 Semaphore、Thread、Waitable timer等

返回值的结果:

WAIT_ABANDONED 0x00000080:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放内核对象会返回此返回值。

WAIT_OBJECT_0 0x00000000 :指定的对象发出有信号状态

WAIT_TIMEOUT 0x00000102:等待超时

WAIT_FAILED 0xFFFFFFFF :出现错误,可通过GetLastError得到错误代码

等待多个对象

DWORD WaitForMultipleObjects( 
    DWORD dwCount, //等待线程数量(最多MAXIMUM_WAIT_OBJECTS个,windows定义为64) 
    CONST HANDLE* phObjects, //线程句柄数组指针 
    BOOL fWaitAll, //是否等待全部 
    DWORD dwMilliseconds //等待时间INFINITE
); 

当参数fWaitAll为TRUE时,若所有线程均变为已通知状态则函数返回值为WAIT_OBJECT_0

当参数fWaitAll为FALSE时,返回的值为线程内核对象数组的索引值

基本用法

//定义两个全局的句柄,当然也可以定义成数组 
HANDLE hThread[2]; 
int main(void) { 

    //创建两个线程 
    hThread[0] = CreateThread(NULL, NULL, GetResult, NULL, 0, NULL); 
    hThread[1] = CreateThread(NULL, NULL, GetNum, NULL, 0, NULL); 

    //等待这两个线程执行完毕 
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE); //关闭句柄 
    CloseHandle(hThread1); 
    CloseHandle(hThread2); 
    return 0; 
}

二、原子访问-原子操作

前言

避免多线程计算读写冲突

InterLocked函数-递增

LONG InterlockedIncrement(LONG volatile* Addend)
LONGLONG InterlockedIncrement64(LONGLONG volatile* Addend)

参数

Addend:原子递增变量的指针。

返回值:变量递增后的值

InterLocked函数-递减

LONG InterlockedDecrement(LONG volatile* Addend)
LONGLONG InterlockedDecrement64(LONGLONG volatile* Addend)

参数:

Addend:原子递减变量的指针。

返回值:变量递减后的值

InterLocked函数-加/减法

LONG InterlockedExchangeAdd(LONG volatile* Addend, LONG Value)
LONGLONG InterlockedExchangeAdd64( 
    LONGLONG volatile* Addend, 
    LONGLONG Value
)

参数:

Addend:原子加法变量的指针

Value:变量

返回值:变量变化后的值

InterLocked函数-变量赋值

LONG InterlockedExchange(LONG volatile* Addend, LONG Value)
LONGLONG InterlockedExchange64( 
    LONGLONG volatile* Addend, 
    LONGLONG Value
)

参数:

Addend:原子操作变量的指针。

Value:给Addend赋值的值。

返回值:变量变化前的值

InterLocked函数-指针赋值

PVOID InterlockedExchangePointer(PVOID volatile *Target, PVOID Value);

参数:

Target:二重指针,需要替换的二重指针的值。

Value:用该地址值替换Target中的地址。

返回值:Target在被替换前的地址值

InterLocked函数-比较赋值

LONG InterlockedCompareExchange( 
    LONG* Destination, 
    LONG ExChange, 
    LONG Comperand 
);

参数:

Destination:需要替换的变量地址。

ExChange:给Destination赋值的值。

Comperand:需要比较的值,如果与Destination相同,则替换。

返回值:变化前的值

LONGLONG InterlockedCompareExchange64 ( 
    LONGLONG volatile *Destination, 
    LONGLONG Exchange, 
    LONGLONG Comparand 
);

参数:

Destination:需要替换的变量地址。

ExChange:给Destination赋值的值。

Comperand:需要比较的值,如果与Destination相同,则替换。

返回值:变化前的值

InterLocked函数-比较指针赋值

PVOID InterlockedCompareExchangePointer( 
    PVOID *Destination , 
    PVOID Exchange , 
    PVOID Comparand 
);

参数:

Destination:需要替换的指针的地址。

ExChange:给Destination赋值的值。

Comperand:需要比较的值,如果与Destination相同,则替换。

返回值:变化前的值

位运算

与运算

SHORT InterlockedAnd16(SHORT volatile *Destination, SHORT Value); 
LONG InterlockedAnd(LONG volatile *Destination, LONG Value); 
LONGLONG InterlockedAnd64(LONGLONG volatile *Destination, LONGLONG Value);

参数:

Destination:与Value做按位与运算,并保存结果。

Value:与Destination做按位与运算的值。

返回值:变化前的值。

或运算

SHORT InterlockedOr16(SHORT volatile *Destination, SHORT Value); 
LONG InterlockedOr(LONG volatile *Destination, LONG Value); 
LONGLONG InterlockedOr64(LONGLONG volatile *Destination, LONGLONG Value);

参数:

Destination:与Value做按位或运算,并保存结果。

Value:与Destination做按位或运算的值。

返回值:变化前的值

异或运算

SHORT InterlockedXor16(SHORT volatile *Destination, SHORT Value); 
LONG InterlockedXor(LONG volatile *Destination, LONG Value); 
LONGLONG InterlockedXor64(LONGLONG volatile *Destination, LONGLONG Value);

参数:

Destination:与Value做按位异或运算,并保存结果。

Value:与Destination做按位异或运算的值。

返回值:变化前的值

三、关键段-临界区

定义关键段:CRITICAL_SECTION cs; 

    初始化关键段:
    InitializeCriticalSection(&cs); | InitializeCriticalSectionEx; | InitializeCriticalSectionAndSpinCount

        进入关键段:EnterCriticalSection(&cs); 

        离开关键段:LeaveCriticalSection(&cs); 

    销毁关键段:DeleteCriticalSection(&cs);
初始化关键段: 
void InitializeCriticalSection( 
    LPCRITICAL_SECTION lpCriticalSection //关键段地址
); 

销毁关键段: 
void DeleteCriticalSection( 
    LPCRITICAL_SECTION lpCriticalSection //关键段地址
);
进入关键段:
void EnterCriticalSection( 
    LPCRITICAL_SECTION lpCriticalSection 
); 

离开关键段:
void LeaveCriticalSection( 
    LPCRITICAL_SECTION lpCriticalSection 
);

简单的例子

CRITICAL_SECTION g_cs; //定义 
int main(void) 
{ 
    InitializeCriticalSection(&g_cs); //初始化 
    HANDLE hThread[2]; 

    hThread[0] = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadProc, NULL, 0, NULL); hThread[1] = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadProc, NULL, 0, NULL); 

    Sleep(1000); 
    g_bFlag = FALSE; 
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE); 
    DeleteCriticalSection(&g_cs); //销毁
    CloseHandle(hThread[0]); 
    CloseHandle(hThread[1]); 
    printf("\ng_iCount1 = %d\n", g_iCount1); 
    printf("\ng_iCount2 = %d\n", g_iCount2); 
    return 0; 
}

DWORD WINAPI ThreadProc(LPVOID *lparam) 
{ 
    EnterCriticalSection(&g_cs); //进入关键段
    while (g_bFlag) { 
        g_iCount1++; 
        g_iCount2++; 
    }
    LeaveCriticalSection(&g_cs); //离开关键段
    return 0; 
}

工作原理

等待状态,消耗系统资源

image-20211126185744841

优化

在内核模式进行检测N次:关键段是否可用,而不会直接退出内核模式

当然,超过检测次数,还是要进入等待状态的

单核CPU无效

image-20211126190002535

在用户模式进行检测关键段

与CPU内核数量无关(推荐使用)

image-20211126190229956

初始化关键段

BOOL InitializeCriticalSectionAndSpinCount( 
    LPCRITICAL_SECTION lpCriticalSection, //关键段地址
    DWORD dwSpinCount //检测次数
);

初始化关键段

BOOL WINAPI InitializeCriticalSectionEx( 
    LPCRITICAL_SECTION lpCriticalSection, 
    DWORD dwSpinCount, 
    DWORD Flags //CRITICAL_SECTION_NO_DEBUG_INFO 不会显示debug信息
);

重新设置检测次数,返回值为设置前的检测次数

DWORD SetCriticalSectionSpinCount( 
    LPCRITICAL_SECTION lpCriticalSection, 
    DWORD dwSpinCount 
); 

尝试进入关键段,关键段是否可用判断

BOOL TryEnterCriticalSection( 
_Inout_ LPCRITICAL_SECTION lpCriticalSection 
);

可能的错误

关键段初始化:

InitializeCriticalSection(&cs); 返回值void

1.内存不足时可能失败

2.线程首次争抢时产生事件内核对象可能失败

处理

关键段初始化:

BOOL InitializeCriticalSectionAndSpinCount( 
    LPCRITICAL_SECTION lpCriticalSection, 
    DWORD dwSpinCount 
);

返回值可以判断关键段初始化是否成功

dwSpinCount:有效范围 0x00000000~0x00FFFFFF

dwSpinCount:初始化即生成事件对象,最高位设置为1

0x80000000~0x80FFFFFF

四、读写锁SRWLOCK

前言

独占锁和共享锁中,后者速度更快!!

定义读写锁:SRWLOCK sl; 

    初始化读写锁:InitializeSRWLock(&sl); 

        //比如写操作
        获取独占读写锁:AcquireSRWLockExclusive(&sl); 

        释放独占读写锁:ReleaseSRWLockExclusive(&sl); 

        不存在销毁读写锁的API 

        //比如读操作
        获取共享读写锁:AcquireSRWLockShared(&sl); 

        释放共享读写锁:ReleaseSRWLockShared(&sl);

初始化读写锁

VOID InitializeSRWLock( 
    PSRWLOCK SRWLock 
);

进入独占锁

VOID AcquireSRWLockExclusive( 

    PSRWLOCK SRWLock 
); 

释放独占锁

VOID ReleaseSRWLockExclusive( 

    PSRWLOCK SRWLock 
);

进入共享锁

VOID AcquireSRWLockShared( 

    PSRWLOCK SRWLock 
); 

释放共享锁

VOID ReleaseSRWLockShared( 

    PSRWLOCK SRWLock 
);

简单的例子

SRWLOCK g_sl; 
int main(void) { 
    InitializeSRWLock(&g_sl); 
    HANDLE hThread[3]; 

    hThread[0] = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadWrite, NULL, 0, NULL); hThread[1] = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadWrite2, NULL, 0, NULL); hThread[2] = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)ThreadRead, NULL, 0, NULL); 

    Sleep(1000); 
    g_bFlag = FALSE; 

    WaitForMultipleObjects(3, hThread, TRUE, INFINITE); 

    CloseHandle(hThread[0]); 
    CloseHandle(hThread[1]); 
    CloseHandle(hThread[2]); 

    printf("\n--g_iCount1 = %d\n", g_iCount1); 
    printf("\n--g_iCount2 = %d\n", g_iCount2); 

    return 0; 
}

DWORD WINAPI ThreadWrite(LPVOID *lparam) 
{ 
    while (g_bFlag) 
    { 
        AcquireSRWLockExclusive(&g_sl); 
        g_iCount1++; 
        g_iCount2++; 
        ReleaseSRWLockExclusive(&g_sl); 
    }
        return 0; 
}

DWORD WINAPI ThreadWrite2(LPVOID *lparam) 
{ 
    while (g_bFlag) 
    { 
        AcquireSRWLockExclusive(&g_sl); 
        g_iCount1++; 
        g_iCount2++; 
        ReleaseSRWLockExclusive(&g_sl); 
    }
    return 0; 
}

DWORD WINAPI ThreadRead(LPVOID *lparam) { 
    while (g_bFlag) 
    { 
        AcquireSRWLockShared(&g_sl); 
        printf("\ng_iCount1 = %d\n", g_iCount1); 
        printf("\ng_iCount2 = %d\n", g_iCount2); 

        ReleaseSRWLockShared(&g_sl); 
    }
    return 0; 
}

五、事件内核对象Event

创建事件内核对象

内核对象可以共享

HANDLE CreateEvent( 

    LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性

    BOOL bManualReset, 

    BOOL bInitialState, 

    LPCTSTR lpName //当前内核对象的名称
); 

重要参数:

bManualReset:手动重置(TRUE),自动重置(FALSE)

bInitialState:初始状态(TRUE:已触发,FALSE:未触发)

微软提出的扩展函数

HANDLE CreateEventEx( 
    LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性

    LPCTSTR lpName, //当前内核对象的名称

    DWORD dwFlags, 

    DWORD dwDesiredAccess //权限访问
);

参数:

dwFlags两个参数 对应bManualReset,bInitialState

dwFlags:CREATE_EVENT_MANUAL_RESET(1) ,CREATE_EVENT_INITIAL_SET(2)

​ bManualReset , bInitialState

打开事件内核对象

HANDLE OpenEvent( 

    DWORD dwDesiredAccess, 

    BOOL bInheritHandle, 

    LPCTSTR lpName 

);

设置事件内核对象,设置状态为已触发

BOOL SetEvent( 

    HANDLE hEvent 

);

设置事件内核对象,设置状态为未触发

当我们把Event创建为自动重置时,Event执行完成后,系统会自动把Event状态重置为未触发状态

BOOL ResetEvent( 

    HANDLE hEvent 

);

简单的例子

HANDLE g_hEvent; 

int main(void) 

{

g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //自动重置,初态为未触发 

SetEvent(g_hEvent); //触发事件对象 

HANDLE hThread; 

hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)BaoShu1, NULL, 0, NULL); 

WaitForSingleObject(hThread, INFINITE); 

CloseHandle(g_hEvent); 

CloseHandle(hThread); 

return 0; 

}

//线程运行完毕后自动设置为未触发 

DWORD WINAPI BaoShu1(LPVOID *lparam) 

{ 

WaitForSingleObject(g_hEvent, INFINITE);

for (int i = 0; i < 10; i++) 

{ 

printf("1\n"); 

}

return 0; 

}

六、可等待计时器WaitableTimer

前言

创建可等待计时器:CreateWaitableTimer 

创建可等待计时器:CreateWaitableTimerEx 

打开可等待计时器:OpenWaitableTimer 

设置可等待计时器:SetWaitableTimer 

取消可等待计时器:CancelWaitableTimer

创建可等待计时器

HANDLE CreateWaitableTimer( 
    LPSECURITY_ATTRIBUTES lpTimerAttributes, 
    BOOL bManualReset, 
    LPCTSTR lpTimerName 
);

参数:

lpTimerAttributes: 安全属性设置

bManualReset: TRUE:手动重置

lpTimerName: 计时器名称,为了跨线程,跨进程

创建可等待计时器:

HANDLE CreateWaitableTimerEx( 
    LPSECURITY_ATTRIBUTES lpTimerAttributes, //安全属性设置 
    LPCTSTR lpTimerName, //计时器名称
    DWORD dwFlags, 
    DWORD dwDesiredAccess //访问权限
);

参数:

dwFlags:

CREATE_WAITABLE_TIMER_MANUAL_RESET(0x00000001)手动重置 

                                   0x00000000 自动重置

dwDesiredAccess:

image-20211127174632825

image-20211127174705478

打开可等待计时器

HANDLE OpenWaitableTimer( 
    DWORD dwDesiredAccess, //访问权限
    BOOL bInheritHandle, //是否可以继承句柄
    LPCTSTR lpTimerName //计时器名称
);

设置可等待计时器

BOOL SetWaitableTimer( 
    HANDLE hTimer, //1.创建时的句柄  2.打开时的句柄
    const LARGE_INTEGER *pDueTime, //首次触发 
    LONG lPeriod, //触发频率间隔 
    PTIMERAPCROUTINE pfnCompletionRoutine, 
    LPVOID lpArgToCompletionRoutine, 
    BOOL fResume //是否提醒
);

LARGE_INTEGER说明:

typedef union _LARGE_INTEGER { 
    struct { 
        DWORD LowPart; 
        LONG HighPart; 
    };
    struct { 
        DWORD LowPart; 
        LONG HighPart; 
    } u; 
    LONGLONG QuadPart; 
} LARGE_INTEGER, *PLARGE_INTEGER;

pDueTime:首次触发时间

指定首次触发时间点:

struct { 
    DWORD LowPart; 
    LONG HighPart; 
}; 

指定启动后触发间隔:LONGLONG QuadPart; 单位:100ns

1秒 = 1000毫秒 1毫秒 = 1000微秒 1微秒 = 1000纳秒(ns)

1秒 = 10000000 * 100ns

lPeriod:触发频率间隔,单位:毫秒,0:不频率性触发 
pfnCompletionRoutine:APC函数 传入的是一个函数指针

APC (Asynchronous procedure call )异步过程调用函数 
lpArgToCompletionRoutine:APC函数参数

取消可等待计时器

BOOL CancelWaitableTimer( 
    HANDLE hTimer 
);

简单例子

int main() 
{ 
    HANDLE hTimer = NULL; 

    LARGE_INTEGER liDueTime; 
    liDueTime.QuadPart = -50000000LL; //设置时间间隔
    hTimer = CreateWaitableTimer(NULL, TRUE, NULL); //创建可等待计时器,手动重置

    if (NULL == hTimer) 
    { 
        printf("CreateWaitableTimer failed (%d)\n", GetLastError()); 
        return 1; 
    }

    printf("Waiting for 5 seconds...\n");

    if (!SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, 0)) //设置可等待计时器 
    { 
        printf("SetWaitableTimer failed (%d)\n", GetLastError()); 
        return 2; 
    }

    if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0) 
        printf("WaitForSingleObject failed (%d)\n", GetLastError()); 

    else printf("Timer was signaled.\n"); 

    return 0; 
}

与用户计时器的差别

用户计时器:需要在应用程序中使用大量的用户界面基础设施,消耗资源更多。 

WM_TIMER优先级最低,只有当线程对象没有任何其它消息的时候才会被处理。
可等待计时器:内核对象,可以多线程间共享,具备安全性。

如果计时器被触发且线程正在等待,那么系统将唤醒线程。

七、信号量semaphore

前言

创建信号量:CreateSemaphore 

创建信号量:CreateSemaphoreEx 

打开信号量:OpenSemaphore 

释放信号量:ReleaseSemaphore

创建信号量

HANDLE CreateSemaphore( 
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, 
    LONG lInitialCount, 
    LONG lMaximumCount, 
    LPCTSTR lpName 
);

参数:

lpTimerAttributes: 安全属性设置

lInitialCount: 初始信号量资源数

lMaximumCount: 最大信号量资源数

lpName: 信号量名称

lInitialCount(初始信号量资源数 ) <= lMaximumCount(最大信号量资源数 )

创建信号量

HANDLE CreateSemaphoreEx( 
    LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性
    LONG lInitialCount, //初始信号量资源数 
    LONG lMaximumCount, //最大信号量资源数 
    LPCTSTR lpName, //信号量名称
    DWORD dwFlags, //保留参数-0
    DWORD dwDesiredAccess //访问权限
);

参数:

dwDesiredAccess

image-20211128123625542

image-20211128130023065

打开信号量:

HANDLE OpenSemaphore( 
    DWORD dwDesiredAccess, //访问权限
    BOOL bInheritHandle, //是否可以继承
    LPCTSTR lpName //信号量名称
);

释放信号量

用来分配资源

释放信号量是累加的,是一个增量

BOOL ReleaseSemaphore( 
    HANDLE hSemaphore, //创建/打开信号量时的返回值
    LONG lReleaseCount, //释放信号量的大小
    LPLONG lpPreviousCount //可用信号量之前的大小
);

原理

image-20211128130855233

八、互斥量mutex/互斥体/互斥锁

前言

创建互斥量:CreateMutex 

创建互斥量:CreateMutexEx 

打开互斥量:OpenMutex 

释放互斥量:ReleaseMutex

创建互斥量

HANDLE CreateMutex( 
    LPSECURITY_ATTRIBUTES lpMutexAttributes, 
    BOOL bInitialOwner, 
    LPCTSTR lpName 
);

参数:

lpTimerAttributes: 安全属性设置

bInitialOwner: 设置互斥量初始归属,TRUE:属于当前线程;FALSE:无归属,为触发状态

lpName: 互斥量名称

创建互斥量

HANDLE CreateMutexEx( 
    LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性设置 
    LPCTSTR lpName, //互斥量名称
    DWORD dwFlags, //设置互斥量初始归属
    DWORD dwDesiredAccess //访问权限
);

参数:

dwFlags: CREATE_MUTEX_INITIAL_OWNER(0x00000001) 当前线程为所有者

​ 0x00000000 当前无所有者

dwDesiredAccess:

image-20211128142504415

image-20211128142517138

打开互斥量

HANDLE OpenMutex( 
    DWORD dwDesiredAccess, 
    BOOL bInheritHandle, 
    LPCTSTR lpName 
);

释放互斥量

返回值是一个BOOL 类型

所以,可能会失败

BOOL ReleaseMutex( 
    HANDLE hMutex 
);

简单例子

HANDLE g_hMutex;

int main(void) 

{ 

    g_hMutex = CreateMutex(NULL,FALSE, NULL); //FALSE:互斥量可触发,并且没有线程占用

    HANDLE hThread; 

    hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)BaoShu, NULL, 0, NULL); 

    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(g_hMutex);
    CloseHandle(hThread); 

    return 0; 
}

DWORD WINAPI BaoShu(LPVOID *lparam) 
{ 
    //等待互斥量资源
    WaitForSingleObject(g_hMutex, INFINITE);

    for (int i = 0; i < 10; i++) 
    { 
        printf("1\n"); 
    }
    //释放互斥量资源
    ReleaseMutex(g_hMutex);
    return 0; 
}

原理

只有互斥量mutex和线程ID 挂钩

image-20211128204607840

比如

image-20211128204906710

当占用互斥量的线程意外死亡?

操作系统本身是可以记录到mutex被哪一个线程所占用

如果线程意外死亡,系统就知道这个线程已经消失了

mutex就变成被遗弃的状态,再也没有办法释放它了

操作系统对象会将mutex的

引用计数置为0

当前线程ID置为0

使得mutex的资源变成一个可以利用的资源

其他系统就可以利用 mutex了,执行自己线程的逻辑

线程同步总结

八大类

image-20211128225007994

简单分类

分为用户模式 / 内核模式

尽量使用用户模式

内核模式的主要作用是跨进程

image-20211128225107993

性能对比

image-20211128225306807

原子访问:使用起来简单方便,维护性好

其他线程同步函数

等待界面程序

但是它对一些程序是没有用的

DWORD WaitForInputIdle( 
    HANDLE hProcess, DWORD dwMilliseconds 
);

等待消息处理

DWORD MsgWaitForMultipleObjects( 
    DWORD nCount, 
    const HANDLE *pHandles, 
    BOOL bWaitAll, 
    DWORD dwMilliseconds, 
    DWORD dwWakeMask 
);
DWORD MsgWaitForMultipleObjectsEx( 
    DWORD nCount, 
    const HANDLE *pHandles, 
    DWORD dwMilliseconds, 
    DWORD dwWakeMask, 
    DWORD dwFlags 
);

边触发边等待-SignalObjectAndWait

DWORD SignalObjectAndWait( 
    HANDLE hObjectToSignal, 
    HANDLE hObjectToWaitOn, 
    DWORD dwMilliseconds, 
    BOOL bAlertable 
);

参数:

hObjectToSignal: 将该对象置为触发状态,必须是互斥量、信号量或者事件

hObjectToWaitOn: 等待该对象互斥量、信号量、事件、计时器、进程、线程、 作业、控制台输入及变更通知。

bAlertable: 表示当线程处于等待状态的时候,是否能够对添加到队列中的异步过程调用进行处理

场景

既需要将一个线程置为触发状态,又需要等另一个线程

向一个对象发送信号 等待

两者结合 就是两个线程的 原子操作

前言
DWORD SignalObjectAndWait( 
    HANDLE hObjectToSignal, 
    HANDLE hObjectToWaitOn, 
    DWORD dwMilliseconds, 
    BOOL bAlertable 
);

参数:

hObjectToSignal: 将该对象置为触发状态,必须是互斥量、信号量或者事件

hObjectToWaitOn: 等待该对象互斥量、信号量、事件、计时器、进程、线程、 作业、控制台输入及变更通知。

bAlertable: 表示当线程处于等待状态的时候,是否能够对添加到队列中的异 步过程调用进行处理。

简单例子
#include <Windows.h>
#include <process.h>
#include <stdio.h.>
#include <stdlib.h>

DWORD WINAPI sayhello(LPVOID *lparam);

HANDLE g_hEvent;

int main(void)
{
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建一个事件内核对象

    HANDLE hThread;
    hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)sayhello, NULL, 0, NULL);

    //之前的操作
    //SetEvent(g_hEvent);
    //WaitForSingleObject(hThread, INFINITE);

    SignalObjectAndWait(g_hEvent, hThread, INFINITE, TRUE);
    for (int i = 0; i < 100; i++)
    {
        printf("world\n");
    }

    CloseHandle(hThread);

    system("pause");
    return 0;
}

DWORD WINAPI sayhello(LPVOID *lparam)
{
    WaitForSingleObject(g_hEvent, INFINITE);
    for (int i = 0; i < 100; i++)
    {
        printf("hello\n");
    }

    return 0;
}

ok 就到这里
希望可以帮到各位师傅!

  • 发表于 2021-12-02 09:35:57
  • 阅读 ( 6286 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
略略略
略略略

36 篇文章

站长统计