1.windows线程
windows线程是可以执行的代码的实例。系统以线程为单位调度程序。
一个程序中可以有多个线程,实现多任务处理。
2.windows线程的特点
(1)线程都有一个ID
(2)线程具有自己的安全属性
(3)每个线程都有自己的内存栈
(4)每个线程都有自己的寄存器信息
3.进程多任务和线程多任务
进程多任务:每个进程都使用私有的地址空间
线程多任务:进程内的多个线程使用同一个地址空间
线程调用:将CPU的执行时间划分为时间片,依次根据时间片执行不同的线程
线程轮询:线程A -> 线程B -> 线程A ...
4.线程使用
(1)定义线程处理函数
DWORD WINAPI ThreadProc( LPVOID lpParameter ); //创建线程时传递给线程的参数
(2)创建线程
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性
SIZE_T dwStackSize, //线程栈大小(小于1M都按1M算)
LPTHREAD_START_ROUTINE lpStartAddress, //线程处理函数地址
LPVOID lpParameter, //传给线程处理函数的参数
DWORD dwCreationFlags, //创建方式
LPDWORD lpThreadId); //创建成功,返回线程ID
创建成功返回线程句柄
创建方式(dwCreationFlags):0 - 创建之后线程立即执行
CREATE_SUSPENDED - 创建之后线程属于挂起(休眠)状态
(3)结束线程
//结束指定线程
BOOL TerminateThread( HANDLE hThread, //线程句柄
DWORD dwExitCode); //退出码
//结束所在线程
VOID ExitThread( DWORD dwExitCode );
(4)关闭线程句柄
CloseHandle(将线程句柄置为-1,只是这个句柄不能用了,跟线程状态没有任何关系)
(5)线程的挂起和执行
//挂起
DWORD SuspendThread( HANDLE hThread );
//执行
DWORD ResumeThread( HANDLE hThread );
(6)线程的信息
//获得当前线程的ID
DWORD GetCurrentThreadId( VOID );
//获得当前线程的句柄
HANDLE GetCurrentThread( VOID );
通过指定ID的线程获得线程句柄
HANDLE OpenThread( DWORD dwDesiredAccess, //访问权限
BOOL bInheritHandle, //继承标识
DWORD dwThreadId); //线程ID
5.多线程问题
线程A -> 线程B -> 线程A ...,例如:线程A打印"******** ",线程B打印"-------- ",会出现错乱情况:
****----****
--********
-------*****
.......,那么,出现的原因是:
当线程A执行printf输出时,如果线程A的执行时间结束,系统会将线程A的相关信息(栈、寄存器)压栈保护,
同时将线程B相关信息恢复,然后执行线程B,线程B继续输出字符。由于线程A正在输出字符,线程B
会继续输出,画面字符会产生混乱。
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" DWORD CALLBACK TestProc(LPVOID pParam) { char *pszText = (char*)pParam; while (1) { printf("%s ", pszText); Sleep(1000); } return 0; } DWORD CALLBACK TestProc2(LPVOID pParam) { char *pszText = (char*)pParam; while (1) { printf("%s ", pszText); Sleep(1000); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { 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); getchar(); SuspendThread(hThread); ResumeThread(hThread2); getchar(); return 0; }
6.线程同步技术
原子锁、临界区、互斥(用于加锁)
事件、信号量、可等候定时器(用于线程同步,线程之间协调工作)
(1)等候函数
//等候单个
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
//等候多个
DWORD WaitForMultipleObjects( DOWRD nCount, //句柄数量
CONST HANDLE *lpHandles, //句柄buff地址
BOOL bWaitAll, //等候方式
DWORD dwMilliseconds); //等候时间
等候方式:TRUE - 所有句柄都有信号,才结束等候
FALSE - 只要有一个有信号,就结束等候
(2)原子锁
a.相关问题
多个线程同时对一个数据进行原子操作,会产生结果丢失。比如++运算时,线程A执行g_nValue++时,如果线程切换时间正好在线程A
将值保存到g_nValue之前,线程B继续执行g_nValue++。那么,当线程A再次切换回来之后,会将原来线程A还未保存的值保存到
g_nValue上,线程B进行的操作就被覆盖。
b.使用
原子锁 - 对单条指令的操作
API:LONG InterlockedIncrement( LPLONG volatile lpAddend );
LONG InterlockedDecrement (LPLONG volatile lpAddend );
LONG InterlockedCompareExchange( LPLONG volatile Destination, LONG Exchange, LONG Comperand );
LONG InterlockedExchange( LONG volatile Target, LONG Value );
...
c.原子锁的实现
直接对数据所在的内存操作,并且任何一个瞬间只能有一个线程访问。
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" DWORD g_nValue = 0; DWORD CALLBACK TestProc1(LPVOID pParam) { for (DWORD i = 0; i < 100000000; i++) { //g_nValue++; InterlockedIncrement(&g_nValue);//原子锁函数 } return 0; } DWORD CALLBACK TestProc2(LPVOID pParam) { for (DWORD i = 0; i < 100000000; i++) { //g_nValue++; InterlockedIncrement(&g_nValue);//原子锁函数 } return 0; } int _tmain(int argc, _TCHAR* argv[]) { DWORD nID = 0; //线程在执行时无信号,执行结束有信号 HANDLE hThread[2] = { 0 }; hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID); hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID); //防止主线程结束 getchar(); //等待所有子线程结束 WaitForMultipleObjects(2, hThread, TRUE, INFINITE); printf("%d ", g_nValue); CloseHandle(hThread[0]); CloseHandle(hThread[1]); return 0; }
(3)临界区
a.相关问题
printf输出混乱,多线程情况下同时使用一段代码。临界区可以锁定一段代码,防止多个线程同时使用该段代码。
b.使用
初始化一个临界区
VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
进入临界区
添加到被锁定的代码之前
VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
离开临界区
添加到被锁定的代码之后
VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
删除临界区
VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
c.临界区和原子锁的区别
原子锁 - 锁定单条指令
临界区 - 锁定单条或多条指令
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" CRITICAL_SECTION cs = { 0 }; DWORD CALLBACK TestProc1(LPVOID pParam) { while (1) { EnterCriticalSection(&cs); //进入临界区 printf("******** "); Sleep(1000); LeaveCriticalSection(&cs); //离开临界区 } return 0; } DWORD CALLBACK TestProc2(LPVOID pParam) { while (1) { EnterCriticalSection(&cs); //进入临界区 printf("-------- "); Sleep(1000); LeaveCriticalSection(&cs); //离开临界区 } return 0; } int _tmain(int argc, _TCHAR* argv[]) { //初始化临界区 InitializeCriticalSection(&cs); DWORD nID = 0; HANDLE hThread[2] = { 0 }; //创建子线程 hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID); hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID); //等待子线程结束 WaitForMultipleObjects(2, hThread, TRUE, INFINITE); //关闭线程句柄 CloseHandle(hThread[0]); CloseHandle(hThread[1]); //删除临界区 DeleteCriticalSection(&cs); return 0; }
(3)互斥
a.相关问题
多线程下代码或资源的共享使用
b.使用
创建互斥
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性
BOOL bInitialOwner, //初始的拥有者
LPCTSTR lpName); //名称
创建成功返回互斥句柄
互斥句柄是可等候句柄(进程句柄、线程句柄是互斥句柄),当任何线程都不拥有互斥句柄时,有信号;被线程拥有时,有信号。
出事拥有者:TRUE - 创建互斥的线程拥有互斥
FALSE - 创建时没有线程拥有互斥
等候互斥
WaitForMultipleObjects
互斥的等候遵循谁先等候谁线获取
释放互斥
BOOL ReleaseMutex( HANDLE hMutex );
关闭互斥句柄
CloseHandle
c.互斥和临界区的区别
临界区 - 用户态,自行效率高,只能在同一个进程中使用
互斥 - 内核态,执行效率低,可以通过命名的方式跨进程使用
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" HANDLE g_hMutex = 0; //接收互斥句柄 DWORD CALLBACK TestProc1(LPVOID pParam) { while (1) { //如果g_hMutex有信号,通过阻塞同时将g_hMutex置为无信号 //如果g_hMetex无信号,则阻塞 WaitForSingleObject(g_hMutex, INFINITE); printf("******** "); Sleep(1000); ReleaseMutex(g_hMutex); } return 0; } DWORD CALLBACK TestProc2(LPVOID pParam) { while (1) { WaitForSingleObject(g_hMutex, INFINITE); printf("-------- "); Sleep(1000); ReleaseMutex(g_hMutex); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { //创建互斥 g_hMutex = CreateMutex(NULL, FALSE, NULL); DWORD nID = 0; HANDLE hThread[2] = { 0 }; hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID); hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(g_hMutex); return 0; }
(4)事件
a.相关问题
程序之间的通知问题
b.使用事件
创建事件
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, //安全属性
BOOL bManualReset, //事件复位方式,TRUE表示手动,FALSE表示自动
BOOL bInitialState, //事件初始状态,TRUE表示有信号
LPCTSTR lpName); //事件名称
成功返回事件句柄,事件句柄是可等候句柄,有无信号,自己控制
等候事件
WaitForSingleObject
触发事件
//将事件设置成有信号状态
BOOL SetEvent( HANDLE hEvent );
//将事件设置成无信号状态
BOOL ResetEvent( HANDLE hEvent );
关闭事件
CloseHandle
注意:小心事件的死锁
自动复位方式是在WaitForSingleObject( handle, INFINITE )函数中实现的
{
......
阻塞代码
......
//判断handle是否为事件句柄
if ( 事件句柄 )
{
//通过事件句柄查看事件的复位方式
if ( 手动 ) { //什么都不干 }
if ( 自动 ) { ResetEvent( handle ); }
}
}
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" HANDLE g_hEvent = 0; DWORD CALLBACK PrintProc(LPVOID pParam) { while (1) { WaitForSingleObject(g_hEvent, INFINITE); ResetEvent(g_hEvent); //事件复位(事件变成无信号) printf("******** "); } return 0; } DWORD CALLBACK CtrlProc(LPVOID pParam) { while (1) { Sleep(1000); SetEvent(g_hEvent); //将事件设置成有信号 } return 0; } int _tmain(int argc, _TCHAR* argv[]) { //创建事件,手动复位,初始无信号 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); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(g_hEvent); return 0; }
(5)信号量
a.相关问题
功能类似于事件,解决通知的相关问题。但是可以提供一个计数器,可以设置次数。
b.使用信号量
创建信号量
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //安全属性
LONG lInitialCount, //信号量的初始计数值
LONG lMaximumCount, //信号量的最大值
LPCTSTR lpName); //名称
创建成功返回信号量句柄(可等候句柄),计数值不为0有信号,为0无信号。
等候信号量
WaitForSingleObject
等候没通过一次,信号量计数值减1,知道为0阻塞
释放信号量(重新设置信号量的计数值)
BOOL ReleaseSemaphore( HANDLE hSemaphore, //信号量句柄
LONG lReleaseCount, //释放数量
LPLONG lpPreviousCount); //原来信号量的数量,可以为NULL
关闭句柄
CloseHandle
相关代码:

#include "stdafx.h" #include "windows.h" #include "stdio.h" HANDLE g_hSemaphore = 0; DWORD CALLBACK TestProc(LPVOID pParam) { while (1) { WaitForSingleObject(g_hSemaphore, INFINITE); printf("******** "); } return 0; } int _tmain(int argc, _TCHAR* argv[]) { g_hSemaphore = CreateSemaphore(NULL, 3, 10, NULL); DWORD nID = 0; HANDLE hThread = CreateThread(NULL, 0, TestProc, NULL, 0, &nID); getchar(); //重新设置计数值,不能超过创建时的最大计数值(10) ReleaseSemaphore(g_hSemaphore, 10, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(g_hSemaphore); return 0; }