win32 - 线程开发

线程

1. 线程基础

  1. 都有一个ID
  2. 每个线程都有自己的内存栈
  3. 同一个进程中所有线程使用同一个地址空间
  4. 按照时间片调度

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. 原子操作:效率高,麻烦
  2. 互斥
  3. 事件
  4. 信号量

1. 原子操作

  1. InterlockedIncrement
  2. InterlockedDecrement
  3. InterlockedCompareExchange
  4. InterlockedExchange
  5. ...

2. 互斥

  1. 创建互斥锁
    WINBASEAPI
    _Ret_maybenull_
    HANDLE  // 返回互斥锁句柄,可等候句柄
    WINAPI
    CreateMutexA(
        _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性,废弃
        _In_ BOOL bInitialOwner, // 初始拥有者 TRUE(创建者拥有这个互斥锁)/ FALSE(创建锁的线程也不拥有互斥)
        _In_opt_ LPCSTR lpName   // 命名,可置空
        );
    
    // 所有线程都不拥有互斥锁时有信号
    // 任何一个线程拥有互斥锁时无信号
  2.  等候互斥
    WaitForSingleObject
    WaitForMultipleObjects
  3. 释放互斥锁
    WINBASEAPI
    BOOL
    WINAPI
    ReleaseMutex(
        _In_ HANDLE hMutex
        );
  4. 关闭互斥句柄
    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. 事件

线程之间通知的问题

  1. 创建事件
    可等候句柄
    WINBASEAPI
    _Ret_maybenull_
    HANDLE  // 返回事件句柄
    WINAPI
    CreateEventA(
        _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
        _In_ BOOL bManualReset,  // 事件重置(有信号变无信号)方式,TRUE 手动,FALSE 自动
        _In_ BOOL bInitialState, // 事件初始状态,TRUE 有信号
        _In_opt_ LPCSTR lpName   // 事件命名,为空即可
        );
  2. 等候事件
    WaitForSingleObject
  3. 触发事件(将事件设置成有信号)
    WINBASEAPI
    BOOL
    WINAPI
    SetEvent(
        _In_ HANDLE hEvent
        );
  4. 复位事件(将事件设置成无信号状态)
    WINBASEAPI
    BOOL
    WINAPI
    ResetEvent(
        _In_ HANDLE hEvent
        );
  5. 关闭事件
    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. 信号量

  1.  创建
    WINBASEAPI
    _Ret_maybenull_
    HANDLE
    WINAPI
    CreateSemaphoreA(
        _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
        _In_     LONG lInitialCount, // 初始化数量
        _In_     LONG lMaximumCount, // 最大值
        _In_opt_ LPCSTR lpName       // 命名
        );
  2. 等候
    WaitForSingleObject
  3. 给信号量指定计数值
    WINBASEAPI
    BOOL
    WINAPI
    ReleaseSemaphore(
        _In_ HANDLE hSemaphore,  // 信号量句柄
        _In_ LONG lReleaseCount, // 释放数量,如果数量超过最大值,释放失败
        _Out_opt_ LPLONG lpPreviousCount // 记录释放前的旧信号量数量,可为NULL
        );
  4. 关闭句柄
    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

前几种方案是内核态的同步方案,临界区是用户态同步方案。

 

posted @ 2022-07-31 23:10  某某人8265  阅读(98)  评论(0编辑  收藏  举报