win32 - 线程开发
线程
1. 线程基础
- 都有一个ID
- 每个线程都有自己的内存栈
- 同一个进程中所有线程使用同一个地址空间
- 按照时间片调度
2. 创建线程
WINBASEAPI
_Ret_maybenull_
HANDLE // 创建成功返回线程句柄
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性,废弃
_In_ SIZE_T dwStackSize, // 栈大小,1M 的整数倍
_In_ LPTHREAD_START_ROUTINE lpStartAddress, // 线程处理函数的地址
_In_opt_ __drv_aliasesMem LPVOID lpParameter, // 传给处理函数的参数
_In_ DWORD dwCreationFlags, // 创建方式,两种:立即执行 0 / 挂起 CREATE_SUSPENDED
_Out_opt_ LPDWORD lpThreadId // 成功时返回的线程ID
);
// 线程处理函数的原型
DWORD WINAPI ThreadProc(LPVOID lpParameter);
例子:
#include <iostream>
#include <Windows.h>
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
char* pszText = (char*)lpParam;
for (;;)
{
std::cout << pszText << std::endl;
Sleep(1000);
}
return 0;
}
int main()
{
const char* pszText = "12345";
DWORD nID = 0;
CreateThread(
NULL,
0,
ThreadProc,
(LPVOID)pszText,
0, // 立即执行,CREATE_SUSPENDED 表示挂起
&nID);
for (;;)
{
std::cout << "main" << std::endl;
Sleep(1000);
}
return 0;
}
3. 线程挂起、销毁
WINBASEAPI
DWORD
WINAPI
SuspendThread(
_In_ HANDLE hThread
);
WINBASEAPI
DWORD
WINAPI
ResumeThread(
_In_ HANDLE hThread
);
操作系统中挂起和阻塞的区别如下:
一:挂起是一种主动行为,因此恢复也应该要主动完成,而阻塞则是一种被动行为,是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列;
二:阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行,一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试;
三:pend是task主动去等一个事件,或消息.suspend是直接悬挂task,以后这个task和你没任何关系,任何task间的通信或者同步都和这个suspended task没任何关系了,除非你resume task;
四:任务调度是操作系统来实现的,任务调度时,直接忽略挂起状态的任务,但是会顾及处于pend下的任务,当pend下的任务等待的资源就绪后,就可以转为ready了。ready只需要等待CPU时间,当然,任务调度也占用开销,但是不大,可以忽略。可以这样理解,只要是挂起状态,操作系统就不在管理这个任务了;
五:挂起是主动的,一般需要用挂起函数进行操作,若没有resume的动作,则此任务一直不会ready。而阻塞是因为资源被其他任务抢占而处于休眠态。两者的表现方式都是从就绪态里“清掉”,即对应标志位清零,只不过实现方式不一样。
// 结束指定线程
WINBASEAPI
BOOL
WINAPI
TerminateThread(
_In_ HANDLE hThread,
_In_ DWORD dwExitCode // 退出状态,给os看的,一般无用。填0即可。下同
);
// 结束函数所在的线程
WINBASEAPI
DECLSPEC_NORETURN
VOID
WINAPI
ExitThread(
_In_ DWORD dwExitCode
);
4. 线程相关操作
WINBASEAPI
HANDLE
WINAPI
GetCurrentThread(
VOID
);
WINBASEAPI
DWORD
WINAPI
GetCurrentThreadId(
VOID
);
等待信号,线程运行时无信号,线程结束的那一刻产生信号:
// 等候可等候的单个句柄的信号
// 可等候句柄:存在有信号和无信号两种状态。如 CreateThread 返回的句柄
WINBASEAPI
DWORD
WINAPI
WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds // 最大等候时间
);
// 等候多个句柄有信号
WINBASEAPI
DWORD
WINAPI
WaitForMultipleObjects(
_In_ DWORD nCount, // 句柄数量
_In_reads_(nCount) CONST HANDLE* lpHandles, // 句柄BUFF的地址
_In_ BOOL bWaitAll, // 等候方式
_In_ DWORD dwMilliseconds // 等候时间 无限大时间: INFINITE
);
// bWaitAll - 等候方式:
// TRUE:所有句柄都有信号才结束
// FALSE:只要有一个句柄有信号就结束
线程交互
互斥:原子操作、互斥锁
同步:事件、信号量
- 原子操作:效率高,麻烦
- 互斥
- 事件
- 信号量
1. 原子操作
- InterlockedIncrement
- InterlockedDecrement
- InterlockedCompareExchange
- InterlockedExchange
- ...
2. 互斥
- 创建互斥锁
WINBASEAPI _Ret_maybenull_ HANDLE // 返回互斥锁句柄,可等候句柄 WINAPI CreateMutexA( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性,废弃 _In_ BOOL bInitialOwner, // 初始拥有者 TRUE(创建者拥有这个互斥锁)/ FALSE(创建锁的线程也不拥有互斥) _In_opt_ LPCSTR lpName // 命名,可置空 ); // 所有线程都不拥有互斥锁时有信号 // 任何一个线程拥有互斥锁时无信号
- 等候互斥
WaitForSingleObject WaitForMultipleObjects
- 释放互斥锁
WINBASEAPI BOOL WINAPI ReleaseMutex( _In_ HANDLE hMutex );
- 关闭互斥句柄
WINBASEAPI BOOL WINAPI CloseHandle( _In_ _Post_ptr_invalid_ HANDLE hObject );
多进程不加锁:
#include <iostream>
#include <Windows.h>
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
for (;;)
{
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::endl;
Sleep(1000);
}
return 0;
}
int main()
{
CreateThread(NULL, 0, ThreadProc, (LPVOID)"1", 0, NULL);
CreateThread(NULL, 0, ThreadProc, (LPVOID)"2", 0, NULL);
getchar();
return 0;
}
加上互斥锁:
#include <iostream>
#include <Windows.h>
HANDLE g_hMutex = NULL;
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
for (;;)
{
WaitForSingleObject(g_hMutex, INFINITE);
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::endl;
ReleaseMutex(g_hMutex);
Sleep(1000);
}
return 0;
}
int main()
{
g_hMutex = CreateMutex(NULL, FALSE, NULL);
CreateThread(NULL, 0, ThreadProc, (LPVOID)"1", 0, NULL);
CreateThread(NULL, 0, ThreadProc, (LPVOID)"2", 0, NULL);
getchar();
return 0;
}
3. 事件
线程之间通知的问题
- 创建事件
可等候句柄
WINBASEAPI _Ret_maybenull_ HANDLE // 返回事件句柄 WINAPI CreateEventA( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, _In_ BOOL bManualReset, // 事件重置(有信号变无信号)方式,TRUE 手动,FALSE 自动 _In_ BOOL bInitialState, // 事件初始状态,TRUE 有信号 _In_opt_ LPCSTR lpName // 事件命名,为空即可 );
- 等候事件
WaitForSingleObject - 触发事件(将事件设置成有信号)
WINBASEAPI BOOL WINAPI SetEvent( _In_ HANDLE hEvent );
- 复位事件(将事件设置成无信号状态)
WINBASEAPI BOOL WINAPI ResetEvent( _In_ HANDLE hEvent );
- 关闭事件
CloseHandle
#include <iostream>
#include <Windows.h>
#include <string.h>
#define THREADS_NUM 5
HANDLE g_Events[THREADS_NUM];
DWORD CALLBACK ThreadProc(LPVOID lpParam)
{
int i = atoi((char*)lpParam);
for (int j = 0;j < 10; j++)
{
WaitForSingleObject(g_Events[i], INFINITE);
ResetEvent(g_Events[i]); // 手动模式,手动释放
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::ends;
std::cout << (char *)lpParam << std::endl;
SetEvent(g_Events[(i + 1) % THREADS_NUM]);
Sleep(100);
}
return 0;
}
int main()
{
for (int i = 0; i < THREADS_NUM; i++)
{
//g_Events[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // 自动模式,无需手动释放
g_Events[i] = CreateEvent(NULL, TRUE, FALSE, NULL); // 手动模式,手动释放
}
SetEvent(g_Events[0]);
HANDLE hThreads[THREADS_NUM] = { 0 };
char nThreadsID[THREADS_NUM][10] = {0};
for (int i = 0; i < THREADS_NUM; i++) {
wsprintf((LPSTR)(nThreadsID + i * 10), "%i", i);
hThreads[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)(nThreadsID + i * 10), 0, NULL);
}
WaitForMultipleObjects(THREADS_NUM, hThreads, TRUE, INFINITE);
for (int i = 0; i < THREADS_NUM; i++)
{
CloseHandle(g_Events[i]);
}
return 0;
}
4. 信号量
- 创建
WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateSemaphoreA( _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, _In_ LONG lInitialCount, // 初始化数量 _In_ LONG lMaximumCount, // 最大值 _In_opt_ LPCSTR lpName // 命名 );
- 等候
WaitForSingleObject - 给信号量指定计数值
WINBASEAPI BOOL WINAPI ReleaseSemaphore( _In_ HANDLE hSemaphore, // 信号量句柄 _In_ LONG lReleaseCount, // 释放数量,如果数量超过最大值,释放失败 _Out_opt_ LPLONG lpPreviousCount // 记录释放前的旧信号量数量,可为NULL );
- 关闭句柄
CloseHandle
#include <iostream>
#include <Windows.h>
#include <string.h>
HANDLE g_hSema = NULL;
DWORD WINAPI consumer(LPVOID lParam)
{
for (;;)
{
DWORD err = WaitForSingleObject(g_hSema, 500);
if (err == WAIT_TIMEOUT)
{
break;
}
printf("P\n");
Sleep(300);
}
return 0;
}
DWORD WINAPI producer(LPVOID lParam)
{
for (int i = 0; i < 20; i++)
{
ReleaseSemaphore(g_hSema, 1, NULL);
printf("V\n");
Sleep(500);
}
return 0;
}
int main()
{
g_hSema = CreateSemaphore(NULL, 0, 5, NULL);
HANDLE hProducer = CreateThread(NULL, 0, producer, NULL, 0, NULL);
HANDLE hConsumer = CreateThread(NULL, 0, consumer, NULL, 0, NULL);
HANDLE Threads[2] = { hConsumer, hProducer };
WaitForMultipleObjects(2, Threads, TRUE, INFINITE);
CloseHandle(g_hSema);
return 0;
}
CRITICAL_SECTION
前几种方案是内核态的同步方案,临界区是用户态同步方案。