> 文章列表 > Windows线程开发

Windows线程开发

Windows线程开发

Windows线程开发

Unit01线程

  • 加锁机制:原子锁、互斥锁
  • 协调机制:信号量、事件

01线程基础

  • Windows线程是可以执行的代码的实例。系统是以线程为单位调度程序。一个程序中可以有多个线程,实现多任务的处理
  • Windows线程的特点:
    • 线程都具有一个ID
    • 每个线程都具有自己的内存栈
    • 同一进程中的线程使用同一个地址空间(栈空间例外)
  • 线程的调度:将CPU的执行时间划分成时间片,依次根据时间片执行不同的线程
    • 线程轮询:线程A->线程B->线程A…(分时复用)

02创建线程

  • 创建线程
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpsa,//安全属性(在windows开发中安全属性都是废弃的参数)DWORD cbStack, //线程栈的大小(按照1M对齐)LPTHREAD_START_ROUTINE lpStartAddr,//线程处理函数的函数地址LPVOID lpvThreadParam, //传递给线程处理函数的参数DWORD fdwCreate,//线程的创建方式(立即执行方式,挂起方式)LPDWORD lpIDThread//创建成功,返回线程的ID
); //创建成功,返回线程句柄

在这里插入图片描述- 定义线程处理函数

DWORD ThreadProc(LPVOID lpParameter//创建线程时,传递给线程的参数(创建线程时填入的lpvThreadParam)
);

示例代码:

#include <Windows.h>
#include <stdio.h>DWORD CALLBACK TestProc(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}int main(){DWORD nID = 0;char* pszText = "************";HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);char* pszText2 = "-----------";HANDLE hThread2 = CreateThread(NULL,0,TestProc2,pszText2,0,&nID);getchar();//让主线程不结束,能让子线程执行return 0;
}

03线程挂起/销毁

  • 挂起(休眠)
DWORD SuspendThread(HANDLE hThread//handle of thread
); 
  • 唤醒
DWORD ResumeThread(HANDLE hThread//handle of thread
); 

示例代码:按回车键切换两个线程的状态

#include <Windows.h>
#include <stdio.h>DWORD CALLBACK TestProc(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}int main(){DWORD nID = 0;char* pszText = "************";HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);char* pszText2 = "-----------";HANDLE hThread2 = CreateThread(NULL,0,TestProc2,pszText2,CREATE_SUSPENDED,&nID);//CREATE_SUSPENDED让线程休眠getchar();//按回车键切换线程状态SuspendThread(hThread);ResumeThread(hThread2);getchar();//让主线程不结束,能让子线程执行return 0;
}

在这里插入图片描述

  • 销毁线程
    • 结束指定线程
    • 结束函数所在的线程
//结束指定线程(相当于他杀)
BOOL TerminateThread(HANDLE hThread,//handle of threadDWORD dwExitCode//exit code
);
//结束函数所在的线程(相当于线程自杀)
VOID ExitThread(DWORD dwExitCode//exit code for this thread(这个参数没有实际的意义)
); 

04线程相关操作

  • 获取当前线程的ID
DWORD GetCurrentThreadId(void);
  • 获取当前线程的句柄
HANDLE GetCurrentThread(void);
  • 等待单个句柄有信号(线程是可等候的句柄):线程执行的过程中是线程句柄无信号的,线程结束的那一刻线程句柄才有信号
DWORD WaitForSingleObject(HANDLE hHandle,//句柄BUFF的地址DWORD dwMilliseconds//最大等候时间,等待时间INFINITE无限
); 
  • 同时等候多个句柄有信号
DWORD WaitForMultipleObjects(DWORD nCount, //句柄数量CONST HANDLE* lpHandles,//句柄BUFF的地址 BOOL bWaitAll,//等待方式DWORD dwMilliseconds//等待时间,INFINITE表示无限时长
);
//bWaitAll:等待方式//TRUE:表示所有句柄都有信号,才结束等待//表示句柄中只要有1个有信号,就结束等待

示例代码:

#include <Windows.h>
#include <stdio.h>DWORD CALLBACK TestProc(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){char* pszText = (char*)pParam;while(1){printf("%s\\n", pszText);Sleep(1000);}return 0;
}int main(){DWORD nID = 0;char* pszText = "************";HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);//因为线程1执行的函数内部是while死循环所以线程句柄不会产生信号,//而等待时间无限,导致程序在这里阻塞,线程2不可能创建WaitForSingleObject(hThread, INFINITE);char* pszText2 = "-----------";HANDLE hThread2 = CreateThread(NULL,0,TestProc2,pszText2,0,&nID);getchar();//按回车键切换线程状态SuspendThread(hThread);ResumeThread(hThread2);getchar();//让主线程不结束,能让子线程执行return 0;
}

Unit02线程同步之原子锁

01原子锁

  • 相关问题:多个线程对统一数据进行原子操作,会产生结果丢失。比如执行++运算时
#include <Windows.h>
#include <stdio.h>long g_value = 0;DWORD CALLBACK TestProc1(LPVOID pParam){for(int i=0; i<10000; i++){g_value++;}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){for(int i=0; i<10000; i++){g_value++;}return 0;
}int main(){DWORD  nID = 0;HANDLE hThread[2];hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);printf("%d\\n",g_value);return 0;
}

在这里插入图片描述

  • 错误代码分析:当线程A执行g_value++时,如果线程切换时间正好是线程A将只保存到g_value之前,线程B继续执行g_value++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_value上,线程V进行的加法操作将会被覆盖
    在这里插入图片描述
  • 使用原子锁函数
LONG InterlockedIncrement(IN PLONG  Addend
);LONG InterlockedDecrement(IN PLONG  Addend
);LONG InterlockedCompareExchange(IN OUT PLONG  Destination,IN LONG  Exchange,IN LONG  Comparand
);LONG InterlockedExchange(IN OUT PLONG  Target,IN LONG  Value
);
//...还有大量的这样的原子锁函数
//原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问

示例代码:

#include <Windows.h>
#include <stdio.h>long g_value = 0;DWORD CALLBACK TestProc1(LPVOID pParam){for(int i=0; i<10000; i++){//g_value++;InterlockedIncrement(&g_value);}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){for(int i=0; i<10000; i++){//g_value++;InterlockedIncrement(&g_value);}return 0;
}int main(){DWORD  nID = 0;HANDLE hThread[2];hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID);hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);printf("%d\\n",g_value);return 0;
}

在这里插入图片描述

Unit03线程同步之互斥

01互斥

  • 相关问题:多线程下代码或资源(临界资源)的共享使用

互斥的使用

  • 创建互斥
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性BOOL bInitialOwner,//初始的拥有者 TRUE/FALSELPCTSTR lpName//命名
);//创建成功返回互斥句柄(互斥句柄是一个可等待的句柄)
//互斥的两个特点://在同一个进程中当某个时间点上只能有一个线程拥有互斥,所以互斥具有独占性和排他性//当所有线程都不用有互斥时,互斥句柄有信号,当有任何一个线程拥有互斥时,互斥句柄无信号
  • 等待互斥:WaitFor...(WaitForSingleObject, WaitForMultipleObjects)互斥的等待遵循谁先等待谁先获取
  • 释放互斥
BOOL ReleaseMutex(HANDLE hMutex//handle of mutex
);
  • 关闭互斥句柄
BOOL CloseHandle(HANDLE hObject
); 

示例代码:解决打印内容乱套现象

  • 未解决前
#include <Windows.h>
#include <stdio.h>DWORD CALLBACK TestProc(LPVOID pParam){char* pszText = (char*)pParam;while(1){//printf("%s\\n", pszText);//Sleep(1000);for(int i=0; i<strlen(pszText); i++){printf("%c", pszText[i]);Sleep(125);}printf("\\n");}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){char* pszText = (char*)pParam;while(1){//printf("%s\\n", pszText);//Sleep(1000);for(int i=0; i<strlen(pszText); i++){printf("%c", pszText[i]);Sleep(125);}printf("\\n");}return 0;
}int main(){DWORD nID = 0;char* pszText = "********";HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);char* pszText2 = "________";HANDLE hThread2 = CreateThread(NULL,0,TestProc2,pszText2,0,&nID);getchar();//让主线程不结束,能让子线程执行return 0;
}

在这里插入图片描述

  • 解决后的代码
#include <Windows.h>
#include <stdio.h>HANDLE g_hMutex = 0;//接收互斥句柄DWORD CALLBACK TestProc(LPVOID pParam){char* pszText = (char*)pParam;while(1){//printf("%s\\n", pszText);//Sleep(1000);//等待获取到互斥锁WaitForSingleObject(g_hMutex,INFINITE);for(int i=0; i<strlen(pszText); i++){printf("%c", pszText[i]);Sleep(125);}printf("\\n");//释放互斥锁ReleaseMutex(g_hMutex);}return 0;
}DWORD CALLBACK TestProc2(LPVOID pParam){char* pszText = (char*)pParam;while(1){//printf("%s\\n", pszText);//Sleep(1000);//等待获取到互斥锁WaitForSingleObject(g_hMutex,INFINITE);for(int i=0; i<strlen(pszText); i++){printf("%c", pszText[i]);Sleep(125);}printf("\\n");//释放互斥锁ReleaseMutex(g_hMutex);}return 0;
}int main(){g_hMutex = CreateMutex(NULL, FALSE, NULL);//FALSE表示创建出来的互斥任何线程都不用有它DWORD nID = 0;char* pszText = "********";HANDLE hThread = CreateThread(NULL,0,TestProc,pszText,0,&nID);char* pszText2 = "________";HANDLE hThread2 = CreateThread(NULL,0,TestProc2,pszText2,0,&nID);getchar();//让主线程不结束,能让子线程执行return 0;
}

在这里插入图片描述

Unit04线程同步之事件

事件

相关问题

  • 程序(线程)之间的通知的问题

事件的使用

  • 创建事件
HANDLE CreateEvent(  LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性BOOL bManualReset,   //事件重置(复位)方式,TRUE手动,FALSE自动,复位的意思就是将事件句柄从有信号变为无信号,如果将事件句柄从无信号变为有信号这叫做“触发”BOOL bInitialState,  //事件初始状态,TRUE有信号,FALSE无信号LPTSTR lpName //事件命名
); //创建成功返回事件句柄
  • 等待事件WaitForSingleObject/WaitForMultipleObject
  • 触发事件(将事件设置为有信号状态)
BOOL SetEvent(HANDLE hEvent //handle of event
); 
  • 复位事件
BOOL ResetEvent(HANDLE hEvent //handle of event
); 
  • 关闭事件CloseHandle

注意:小心事件的死锁

示例代码:用线程2控制线程1的打印速度,每秒打印一次

#include <Windows.h>
#include <stdio.h>
HANDLE g_hEvent = 0;//接收事件句柄DWORD CALLBACK PrintProc(LPVOID pParam){while(1){//1.等到事件有信号WaitForSingleObject(g_hEvent, INFINITE);//3.将事件设置为无信号ResetEvent(g_hEvent);printf("*************\\n");}return 0;
}DWORD CALLBACK CtrlProc(LPVOID pParam){while(1){Sleep(1000);//2.设置事件有信号SetEvent(g_hEvent);}return 0;
}int main(){g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);DWORD nID = 0;HANDLE hThread[2] = {0};hThread[0] = CreateThread(NULL,0,PrintProc,NULL,0,&nID);hThread[1] = CreateThread(NULL,0,CtrlProc,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);return 0;
}

Unit05线程同步之信号量

01信号量

相关问题

类似于事件,解决通知的相关问题。但是提供一个计数器,可以设置次数。

信号量的使用

  • 创建信号量
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性LONG lInitialCount,//初始化信号量数量LONG lMaximumCount,//信号量的最大值(即lInitialCount<=lMaximumCount)LPCTSTR lpName //命名
); //创建成功返回信号量句柄
  • 等待信号量:WaitForSingleObject / WaitForMultipleObjects每等待通过一次,信号量的信号减1,直到为0阻塞
  • 给信号量指定计数值
BOOL ReleaseSemaphore(HANDLE hSemaphore,//信号量句柄LONG lReleaseCount, //释放数量LPLONG lpPreviousCount//释放前原来信号量的数量(即设置新的数量前可用的信号量个数),可以为NULL
);
  • 关闭句柄CloseHandle

示例代码1:让信号量控制打印的次数

#include <Windows.h>
#include <stdio.h>HANDLE g_hSema = 0;DWORD CALLBACK TestProc(LPVOID pParam){while(1){WaitForSingleObject(g_hSema, INFINITE);printf("********\\n");}
}int main(){g_hSema = CreateSemaphore(NULL,3,10,NULL);DWORD nID = 0;HANDLE hThread = CreateThread(NULL,0,TestProc,NULL,0,&nID);WaitForSingleObject(hThread, INFINITE);CloseHandle(g_hSema);return 0;
}
```![在这里插入图片描述](https://img-blog.csdnimg.cn/f4c2e83a5ae24d8b9a84522e0bf8139d.png#pic_center)
示例代码2:让信号量控制打印3次,然后按回车键再打印5次
```c
#include <Windows.h>
#include <stdio.h>HANDLE g_hSema = 0;DWORD CALLBACK TestProc(LPVOID pParam){while(1){WaitForSingleObject(g_hSema, INFINITE);printf("********\\n");}
}int main(){g_hSema = CreateSemaphore(NULL,3,10,NULL);DWORD nID = 0;HANDLE hThread = CreateThread(NULL,0,TestProc,NULL,0,&nID);getchar();//按回车键ReleaseSemaphore(g_hSema,5,NULL);WaitForSingleObject(hThread, INFINITE);CloseHandle(g_hSema);return 0;
}

在这里插入图片描述

示例代码:测试重设值如果大于最大预设值会发生什么现象

#include <Windows.h>
#include <stdio.h>HANDLE g_hSema = 0;DWORD CALLBACK TestProc(LPVOID pParam){while(1){WaitForSingleObject(g_hSema, INFINITE);printf("********\\n");}
}int main(){g_hSema = CreateSemaphore(NULL,3,10,NULL);DWORD nID = 0;HANDLE hThread = CreateThread(NULL,0,TestProc,NULL,0,&nID);getchar();//按回车键ReleaseSemaphore(g_hSema,11,NULL);//这里故意让重设值大于最大预设值10WaitForSingleObject(hThread, INFINITE);CloseHandle(g_hSema);return 0;
}

在这里插入图片描述