• windows编程-线程



    模块功能

    添加当前模块大概功能的描述,希望不要把所有接口文档写在一个文件中,至少按模块分类。

    • 线程
    • 互斥问题
    • 多线程资源抢占问题

    线程的概念与实现方式

    线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。

    从资源分配的角度来看,进程是操作系统进行资源分配的基本单位。
    从资源调度的角度来看,线程是资源调度的最小单位,是程序执行的最小单位
    执行序列就是一组有序指令的集合——函数。

    线程是进程内部的一条执行序列,一个进程至少有一条线程,称之为主线程(main方法代表的执行序列),可以通过线程库创建其他线程(给线程指定执行的函数),将创建的线程称之为函数线程。
    在这里插入图片描述

    线程的实现方式

    内核级线程 由内核直接创建和管理线程,虽然创建开销较大,但是可以利用多处理器的资源
    用户级线程 由线程库创建和管理多个线程,线程的实现都是在用户态,内核无法感知,创建开销较小,无法使用多处理器的资源
    混合级线程 结合以上两种方式实现,可以利用多处理器的资源,从而在用户空间中创建更多的线程,从而映射到内核空间的线程中

    Linux系统实现多线程的方式

    Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。 Linux 把所有的线程都当做进程来实现。
    内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。
    相反,线程仅仅被视为一个与其他进程共享某些资源的进程。
    每个线程都拥有唯 一隶属于自己的task_struct,
    所以在内核中,它看起来就像是一个普通的进程(只是线程和 其他一些进程共享某些资源,如地址空间)

    线程和进程的区别

    进程是资源分配最小单位,线程是程序执行的最小单位;
    线程间的切换效率相比进程间的切换要高
    进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
    创建一个线程比进程开销小;
    线程占用的资源要⽐进程少很多。
    线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
    多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
    进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

    1. 创建线程

    #include 
    #include 
    
    
    DWORD WINAPI ThreadProc(LPVOID lpThreadParameter)
    {
    	DWORD dwParam = (DWORD)lpThreadParameter;
    	int n = 0;
    	while (n<1500)
    	{
    		printf("Hello world:%d\n", n);
    		n++;
    	}
    	return 0;
    }
    
    
    int main()
    {
    	HANDLE hThread = CreateThread(
    		NULL,       //安全描述属性,默认填NULL即可。
    		0,          //栈大小,填写0默认就是1MB
    		ThreadProc, //回调函数地址,线程的起始执行位置
    		(LPVOID)100,          //传给线程回调函数的参数
    		0,          //创建的标识
    		NULL           //线程ID,我们如果不需要,就填NULL
    	);
    	WaitForSingleObject(hThread, -1); //等待线程结束后,结束主线程
    	return 0;
    }
    

    image.png
    2. 枚举线程

    #include 
    #include 
    #include 
    
    int main()
    {
    	THREADENTRY32 te = {sizeof(THREADENTRY32)};
    	//1. 我们创建线程快照的时候,会将当前系统下所有的线程都给照下来。
    	HANDLE hSnap = CreateToolhelp32Snapshot(
    		TH32CS_SNAPTHREAD,
    		0
    	);
    	//2. 得到第一个进程信息
    	BOOL bSuccess = Thread32First(
    		hSnap,  //快照的句柄 
    		&te     //用于获取进程信息的结构体
    	);
    	if (bSuccess == TRUE)
    	{
    		do
    		{
    			if (te.th32OwnerProcessID == 19320) //processID 是进程ID
    			{
    				printf("线程ID为:%d\n", te.th32ThreadID);
    				//1 打开线程得到句柄
    				HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
    				//2. 挂起所有的线程
    				//SuspendThread(hThread);
    				//3. 恢复线程
    				//ResumeThread(hThread);
    				//4. 终止线程
    				TerminateThread(hThread, 0);
    			}
    		} while (Thread32Next(hSnap, &te));
    	}
    
    
    }
    

    image.png

    伪句柄

    #include 
    #include 
    
    
    DWORD WINAPI ThreadProc(LPVOID lpThreadParameter)
    {
    	//1 获取外部传入的句柄
    	HANDLE hThread = (HANDLE)lpThreadParameter;
    
    	//2 根据句柄获取线程的创建时间
    	FILETIME stcCreationTime, stcExitTime;
    	FILETIME stcKernelTime, stcUserTime;
    	
    	GetThreadTimes(hThread, &stcCreationTime,
    		&stcExitTime, &stcKernelTime, &stcUserTime);
    	
    	FILETIME FileTime = { 0 };
    	FileTimeToLocalFileTime(&stcCreationTime, &FileTime);
    	//转为我们能够看懂的时间
    	SYSTEMTIME systime = { 0 };
    	FileTimeToSystemTime(&FileTime, &systime);
    
    
    	return 0;
    }
    
    int main()
    {
    	//1. 获取当前线程的句柄
    	HANDLE hThread1 = GetCurrentThread();
    
    	FILETIME stcCreationTime, stcExitTime;
    	FILETIME stcKernelTime, stcUserTime;
    	GetThreadTimes(hThread1, &stcCreationTime,
    		&stcExitTime, &stcKernelTime, &stcUserTime);
    
    	FILETIME FileTime = { 0 };
    	FileTimeToLocalFileTime(&stcCreationTime, &FileTime);
    	//转为我们能够看懂的时间
    	SYSTEMTIME systime = { 0 };
    	FileTimeToSystemTime(&FileTime, &systime);
    
    
    
    	//2.将句柄传给另外一个线程
    
    	HANDLE hThread2 = CreateThread(
    		NULL,       //安全描述属性,默认填NULL即可。
    		0,          //栈大小,填写0默认就是1MB
    		ThreadProc, //回调函数地址,线程的起始执行位置
    		(LPVOID)hThread1,          //传给线程回调函数的参数
    		0,          //创建的标识
    		NULL           //线程ID,我们如果不需要,就填NULL
    	);
    	WaitForSingleObject(hThread2, -1);
    	return 0;
    }
    
    

    拷贝句柄

    #include 
    #include 
    
    
    DWORD WINAPI ThreadProc(LPVOID lpThreadParameter)
    {
    	//1 获取外部传入的句柄
    	HANDLE hThread = (HANDLE)lpThreadParameter;
    
    	//2 根据句柄获取线程的创建时间
    	FILETIME stcCreationTime, stcExitTime;
    	FILETIME stcKernelTime, stcUserTime;
    
    	GetThreadTimes(hThread, &stcCreationTime,
    		&stcExitTime, &stcKernelTime, &stcUserTime);
    
    	FILETIME FileTime = { 0 };
    	FileTimeToLocalFileTime(&stcCreationTime, &FileTime);
    	//转为我们能够看懂的时间
    	SYSTEMTIME systime = { 0 };
    	FileTimeToSystemTime(&FileTime, &systime);
    
    
    	return 0;
    }
    
    
    
    int main()
    {
    	//1. 获取当前线程的句柄
    	HANDLE hThread1 = GetCurrentThread();
    
    	FILETIME stcCreationTime, stcExitTime;
    	FILETIME stcKernelTime, stcUserTime;
    	GetThreadTimes(hThread1, &stcCreationTime,
    		&stcExitTime, &stcKernelTime, &stcUserTime);
    
    	FILETIME FileTime = { 0 };
    	FileTimeToLocalFileTime(&stcCreationTime, &FileTime);
    	//转为我们能够看懂的时间
    	SYSTEMTIME systime = { 0 };
    	FileTimeToSystemTime(&FileTime, &systime);
    
    
    
    	//2.将句柄复制出来一个,复制出来的就是真句柄
    	HANDLE hRealThread = 0;
    	DuplicateHandle(
    		GetCurrentProcess(), // 拥有源句柄的进程句柄
    		GetCurrentThread(),  // 指定对象的现有句柄(伪句柄)
    
    		GetCurrentProcess(), // 拥有新对象句柄的进程句柄
    		&hRealThread,      // 用于保存新句柄
    		0,                   // 安全访问级别
    		false,               // 是否可以被子进程继承
    		DUPLICATE_SAME_ACCESS); // 转换选项
    
    
    
    	//3.将句柄传给另外一个线程
    	HANDLE hThread2 = CreateThread(
    		NULL,       //安全描述属性,默认填NULL即可。
    		0,          //栈大小,填写0默认就是1MB
    		ThreadProc, //回调函数地址,线程的起始执行位置
    		(LPVOID)hRealThread,          //传给线程回调函数的参数
    		0,          //创建的标识
    		NULL           //线程ID,我们如果不需要,就填NULL
    	);
    	WaitForSingleObject(hThread2, -1);
    	return 0;
    }
    
    

    获取执行环境

    #include 
    #include 
    #include 
    int main()
    {
    	THREADENTRY32 te = { sizeof(THREADENTRY32) };
    	//1. 我们创建线程快照的时候,会将当前系统下所有的线程都给照下来。
    	HANDLE hSnap = CreateToolhelp32Snapshot(
    		TH32CS_SNAPTHREAD,
    		0
    	);
    	//2. 得到第一个进程信息
    	BOOL bSuccess = Thread32First(
    		hSnap,  //快照的句柄 
    		&te     //用于获取进程信息的结构体
    	);
    	if (bSuccess == TRUE)
    	{
    		do
    		{
    			if (te.th32OwnerProcessID == 6600)
    			{
    				printf("线程ID为:%d\n", te.th32ThreadID);
    				//1 打开线程得到句柄
    				HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID);
    				//2. 挂起所有的线程
    				//SuspendThread(hThread);
    				//3. 获取线程环境
    				CONTEXT context = { CONTEXT_FULL };
    				GetThreadContext(hThread, &context);
    				int a = 0;
    				a++;
    			}
    		} while (Thread32Next(hSnap, &te));
    	}
    }
    

    原子操作解决互斥问题

    #include 
    
    
    #include 
    #include 
    
    LONG g_n;
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    		//g_n++;
    		InterlockedIncrement(&g_n);
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    		//g_n++;
    		InterlockedIncrement(&g_n);//原子操作能够保证的事情,当我操作这个数据的时候,别人都不能操作。
    	return 0;
    }
    int main() {
    	HANDLE hThread1 = 0, hThread2;
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	printf("%d", g_n);
    	return 0;
    }
    

    使用临界区解决多线程抢占问题

    #include 
    #include 
    
    CRITICAL_SECTION cs = { 0 };
    
    int g_n;
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		EnterCriticalSection(&cs);//这个操作相当于上锁
    		g_n++;
    		LeaveCriticalSection(&cs);//这个操作相当于开锁
    	}
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		EnterCriticalSection(&cs);//上锁
    		g_n++;
    		LeaveCriticalSection(&cs);//开锁
    	}
    	return 0;
    }
    int main() {
    	InitializeCriticalSection(&cs);
    
    
    	HANDLE hThread1 = 0, hThread2;
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	printf("%d", g_n);
    	return 0;
    }
    
    

    使用互斥体解决多线程抢占问题

    // 09. 使用互斥体解决问题.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include 
    #include 
    HANDLE g_hMutex = 0;
    
    int g_n;
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hMutex, -1);
    		g_n++;
    		ReleaseMutex(g_hMutex);
    	}
    
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hMutex, -1);
    		g_n++;
    		ReleaseMutex(g_hMutex);
    	}
    	return 0;
    }
    int main() {
    
    	g_hMutex = CreateMutex(
    		NULL,  //安全属性
    		FALSE, //创建的线程是不是互斥体的拥有者,互斥体一开始是没有拥有者的,互斥体此时处于激发态
    		NULL   //互斥体的名字,可以用它作为标识打开这个互斥体
    		);
    
    
    	HANDLE hThread1 = 0, hThread2;
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	printf("%d", g_n);
    	return 0;
    }
    

    使用事件对象解决多线程抢占问题

    // 10. 使用事件对象解决问题.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include 
    #include 
    #include 
    HANDLE g_hEvent = 0;
    int g_n;
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hEvent, -1);
    		g_n++;
    		SetEvent(g_hEvent);//
    	}
    
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hEvent, -1);
    		g_n++;
    		SetEvent(g_hEvent);
    	}
    
    	return 0;
    }
    int main() {
    	HANDLE hThread1 = 0, hThread2;
    	g_hEvent = CreateEvent(
    		NULL,  //安全描述属性
    		FALSE, //自动设置激发态和非激发态
    		TRUE,  //初始状态是激发态
    		NULL   //给事件对象起一个名字
    	);
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	printf("%d", g_n);
    	return 0;
    }
    
    

    事件的例子

    // 10. 事件的例子.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include 
    #include 
    HANDLE g_hEvent1 = 0;
    HANDLE g_hEvent2 = 0;
    HANDLE g_hEvent3 = 0;
    int g_n;
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	WaitForSingleObject(g_hEvent1, -1);
    	printf("hello 线程1\n");
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	WaitForSingleObject(g_hEvent2, -1);
    	printf("hello 线程2\n");
    	SetEvent(g_hEvent1);
    	return 0;
    }
    DWORD WINAPI ThreadPro3(LPVOID lpThreadParameter) {
    	WaitForSingleObject(g_hEvent3, -1);
    	printf("hello 线程3\n");
    	SetEvent(g_hEvent2);
    	return 0;
    }
    
    
    
    
    int main()
    {
    	HANDLE hThread1, hThread2, hThread3;
    	g_hEvent1 = CreateEvent(
    		NULL,
    		FALSE,
    		FALSE,//填了FALSE,就是非激发态
    		NULL
    	);
    	g_hEvent2 = CreateEvent(
    		NULL,
    		FALSE,
    		FALSE,
    		NULL
    	);
    	g_hEvent3 = CreateEvent(
    		NULL,
    		FALSE,
    		FALSE,
    		NULL
    	);
    
    	SetEvent(g_hEvent3);
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	hThread3 = CreateThread(NULL, NULL, ThreadPro3, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	WaitForSingleObject(hThread3, -1);
    	return 0;
    }
    

    信号量解决多线程互斥问题

    // 11. 用信号量解决问题.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include 
    
    #include 
    #include 
    
    int g_n;
    HANDLE g_hSemahpore;
    //信号量一般用于解决生产者消费者问题
    //   有多个生产者在生产东西,多个消费者在拿东西,怎么能让这个系统不会出问题。
    //   
    
    DWORD WINAPI ThreadPro1(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hSemahpore, -1);//会将信号数减1,此时就处于非激发态
    											  //锁住了信号量
    		g_n++;
    		ReleaseSemaphore(g_hSemahpore, 1, NULL);
    	}
    
    	return 0;
    }
    DWORD WINAPI ThreadPro2(LPVOID lpThreadParameter) {
    	for (int i = 0; i < 100000; i++)
    	{
    		WaitForSingleObject(g_hSemahpore, -1);
    		g_n++;
    		ReleaseSemaphore(g_hSemahpore, 1, NULL);
    	}
    	return 0;
    }
    int main() {
    
    	g_hSemahpore = CreateSemaphore(
    		NULL,
    		1,  //初识信号数为1,此时处于激发态
    		1,
    		NULL
    	);
    
    	HANDLE hThread1 = 0, hThread2;
    	hThread1 = CreateThread(NULL, NULL, ThreadPro1, NULL, NULL, NULL);
    	hThread2 = CreateThread(NULL, NULL, ThreadPro2, NULL, NULL, NULL);
    	WaitForSingleObject(hThread1, -1);
    	WaitForSingleObject(hThread2, -1);
    	printf("%d", g_n);
    	return 0;
    }
    
    
  • 相关阅读:
    有谁知道这个这么弄吗?
    go 中解析JSON的三种姿势
    基于SpringBoot的体育场运营系统
    Scrapy内容
    为不断增长的Go生态系统扩展gopls
    Linux配置密钥登录、更换SSH端口Windows更改远程桌面端口
    【python入门专项练习】-N06.循环语句
    零基础自学SQL课程 | 窗口函数
    Linux查端口占用的几种方式
    Unity的IPreprocessShaders:深入解析与实用案例
  • 原文地址:https://blog.csdn.net/mengxj168/article/details/126974713