分享者才是学习中最大的受益者!!
线程同步
前言
线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态
简单来说,就是线程之间的协调,线程之间的协同、协助、互相配合完成工作。
一、等待函数
解决的问题
问题一:不同线程函数的执行必须有先后顺序
问题二:同一线程函数中一段代码必须作为一个单元执行
简单的案例
等待函数
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;
}
工作原理
等待状态,消耗系统资源
优化
在内核模式进行检测N次:关键段是否可用,而不会直接退出内核模式
当然,超过检测次数,还是要进入等待状态的
单核CPU无效
在用户模式进行检测关键段
与CPU内核数量无关(推荐使用)
初始化关键段
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:
打开可等待计时器
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
打开信号量:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, //访问权限
BOOL bInheritHandle, //是否可以继承
LPCTSTR lpName //信号量名称
);
释放信号量
用来分配资源
释放信号量是累加的,是一个增量
BOOL ReleaseSemaphore(
HANDLE hSemaphore, //创建/打开信号量时的返回值
LONG lReleaseCount, //释放信号量的大小
LPLONG lpPreviousCount //可用信号量之前的大小
);
原理
八、互斥量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:
打开互斥量
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 挂钩
比如
当占用互斥量的线程意外死亡?
操作系统本身是可以记录到mutex被哪一个线程所占用
如果线程意外死亡,系统就知道这个线程已经消失了
mutex就变成被遗弃的状态,再也没有办法释放它了
操作系统对象会将mutex的
引用计数置为0
当前线程ID置为0
使得mutex的资源变成一个可以利用的资源
其他系统就可以利用 mutex了,执行自己线程的逻辑
线程同步总结
八大类
简单分类
分为用户模式 / 内核模式
尽量使用用户模式
内核模式的主要作用是跨进程
性能对比
原子访问:使用起来简单方便,维护性好
其他线程同步函数
等待界面程序
但是它对一些程序是没有用的
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 就到这里
希望可以帮到各位师傅!
- 本文作者: 略略略
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/939
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!