创建线程的几种方法
C++多线程之_beginthread
https://blog.csdn.net/u013043408/article/details/83830181
C++多线程之CreateThread
https://blog.csdn.net/u013043408/article/details/83830598
C++多线程之std::thread
https://blog.csdn.net/u013043408/article/details/83855565
特征
名字 |
范围 |
作用对象 |
特性 |
原子锁 |
本进程 |
互斥变量 |
windows api |
临界区 |
本进程 |
互斥代码段 |
线程拥有权;旋转锁 |
互斥量 |
进程间 |
互斥代码段 |
线程拥有权;遗弃问题 |
事件 |
进程间 |
同步代码段 |
自动复位 |
信号量 |
进程间 |
同步代码段(只有1个资源时) |
资源计数 |
读写锁 |
本进程 |
互斥代码段 |
windows api |
参考资料:https://blog.csdn.net/morewindows/article/details/17488865
一、原子操作Interlocked
1、头文件 windows.h
2、互斥的对象是一个变量。
3、变量字节对齐?
4、常用接口
【加一,返回Addend+1】
LONG InterlockedIncrement(LONG volatile* Addend);
【减一,返回Addend-1】
LONG InterlockedDecrement(LONG volatile* Addend);
【加n,返回Addend+Value】
LONG InterlockedExchangeAdd(LONG volatile* Addend, LONG Value);
【赋值,返回值为原先的Target,然后Target=Value】
LONG InterlockedExchange(LONG volatile* Target, LONG Value);
二、临界区CS
1、临界区结构体
【OwningThread】拥有临界区的线程。只有拥有者才可以”进入临界区“。当然,其它线程是可以”退出临界区“的(计数-1)。
【RecursionCount】计数。不论哪个线程,进入临界区+1,退出临界区-1(即使是在其它线程退出也-1)。
【LockSemaphore】自复位事件。猜测是,退出临界区时,检查到计数为0,则发送事件,宣布:“这个临界区可以空闲了,谁要使用,先来先得。”。
【SpinCount】旋转锁。一般来说,线程申请拥有临界区使用权失败后,会立即切换到等待状态。等临界区空闲后,才切换到运行状态。而旋转锁的作用是,当申请失败后,线程会继续尝试申请n次。猜想“一个for,循环SpinCount次,申请拥有权”。我们知道,切换线程状态开销较大,如果CPU数量够用,能保证一线程一CPU。那么,子线程完全可以一直死等,直到临界区空闲。而加了旋转锁,在一定程度上避免了线程状态切换,保持线程的连续性。
2、拥有临界区的线程,可以多次进入临界区。但想要归还使用权,则必须进入多少次,就退出多少次。
3、临界区可以解决线程的互斥问题,但因为具有“线程所有权”,所以无法解决同步问题。
4、常用接口
【初始化,定义临界区变量后必须先初始化】
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);
【销毁,用完之后记得销毁】
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
【进入临界区,系统保证各线程互斥的进入临界区】
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
【离开临界区】
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
【修改旋转锁】
DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);
三、互斥量Mutex
1、互斥量是内核对象,它与临界区都有“线程所有权”,所以不能用于线程的同步。
2、互斥量能够用于多个进程之间线程互斥问题,并且能完美的解决某线程意外终止所造成的“遗弃”问题。
3、常用接口
【创建】HANDLE CreateMutex(lpMutexAttributes,bInitialOwner,lpName);
【打开】HANDLE OpenMutex(dwDesiredAccess,bInheritHandle,lpName);
【释放】BOOL ReleaseMutex(hMutex);
【关闭】CloseHandle(hEvent);
四、事件
1、事件是内核对象,分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2、事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3、事件可以解决进程间同步问题,因此也能解决互斥问题。
4、事件没有“线程所有权”,所以连续两次等待同一个事件,第二个会等待超时。
4、常用接口
【创建】HANDLE CreateEvent(lpEventAttributes,bManualReset,bInitialState,lpName);
【打开】HANDLE OpenEvent(dwDesiredAccess,bInheritHandle,lpName);
【置位】BOOL SetEvent(hEvent);
【复位】BOOL ResetEvent(hEvent);
【发脉冲,将事件触发后立即设置为未触发】BOOL PulseEvent(hEvent);
【关闭】CloseHandle(hEvent);
五、信号量
1、信号量是内核对象。
2、当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。
3、可以解决进程间同步问题,因此也能解决互斥问题。
4、常用接口
【创建】HANDLE CreateSemaphore(lpSemaphoreAttributes,lInitialCount,lMaximumCount,lpName);
【打开】HANDLE OpenSemaphore(dwDesiredAccess,bInheritHandle,lpName);
【释放】BOOL ReleaseSemaphore(hMutex);
【关闭】CloseHandle(hEvent);
六、读写锁
1、读写锁声明后要初始化,但不用销毁,系统会自动清理读写锁。
2、读取者和写入者分别调用不同的申请函数和释放函数。
3、一个线程仅能锁定资源一次,不能多次锁定资源。
4、常用接口
【初始化】VOID InitializeSRWLock(SRWLock);
【申请写】VOID AcquireSRWLockExclusive(SRWLock);
【释放写】VOID ReleaseSRWLockExclusive(SRWLock);
【申请读】VOID AcquireSRWLockShared(SRWLock);
【释放读】VOID ReleaseSRWLockShared(SRWLock);
七、WaitForSingleObject返回值
WAIT_ABANDONED:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
WAIT_OBJECT_0:指定的对象有信号状态。
WAIT_TIMEOUT:等待超时。
WAIT_FAILED:出现错误,可通过GetLastError得到错误代码。
八、PV操作
P:消息是否到达
V:发送消息
相当于:
等待事件
置位事件