系统API函数实现多线程及线程同步
Posted Autumn の Box
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统API函数实现多线程及线程同步相关的知识,希望对你有一定的参考价值。
1、线程的创建
须包含头文件:#include <windows.h>
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构体的指针,一般设为NULL,让该线程使用默认的安全性;如果希望所有的子进程能够继承该线程对象的句柄,就必须设定一个SECURITY_ATTRIBUTES结构体,将它的bInheritHandle成员初始化为TRUE;
dwStackSize:设置线程初始化栈的大小,即线程可以将多少地址空间用于它自己的栈,以字节为单位;如果设为0,将使用与调用该函数的线程相同的栈空间大小;
lpStartAddress:指向新线程的入口函数的起始地址;
lpParameter:可通过该参数给新线程传递参数,参数值既可以是一个数值,也可以是一个指向其他信息的指针;
dwCreationFlags:设置新线程的附加标记,如果为CREATE_SUSPENDED,线程创建后处于暂停状态,直到程序调用了ResumeThread函数为止;如果为0,线程在创建之后就立即运行;
lpThreadId:指向一个变量,用来接收新线程的ID,可以为NULL;
新线程的入口函数:
DWORD WINAPI ThreadProc( LPVOID lpParameter );
ThreadProc--为新线程的函数名称,可以改为自定义的线程名称;
2、线程同步
线程同步的方式包括三种:互斥对象、事件对象、关键代码段
比较:
1.互斥对象和事件对象都属于内核对象,利用内核对象进行线程同步时,速度较慢,但利用内核对象可以在多个进程中的各个线程间进行同步;
2.关键代码段工作在用户模式下,同步速度较快,但使用关键代码段时容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值;
2.1互斥对象
2.1.1利用互斥对象实现线程同步
1)创建互斥对象
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
lpMutexAttributes:指向SECURITY_ATTRIBUTES结构的指针,可以设为NULL,让互斥对象使用默认的安全性;
bInitialOwner:如果为TRUE,则创建这个互斥对象的线程获得该对象的所有权;如果为FALSE,该线程将不具有所创建的互斥对象的所有权;
lpName:指定互斥对象的名称,如果为NULL,则创建一个匿名的互斥对象;
返回值:如果该函数调用成功,返回所创建的互斥对象的句柄;如果创建的是命名的互斥对象,且在创建之前已存在该命名的互斥对象,则该函数将返回已存在的这个互斥对象的句柄,调用GetLastError函数将返回ERROR_ALREADY_EXISTS;
2)请求互斥对象的所有权
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
hHandle:所请求的对象的句柄;一旦获得互斥对象的信号状态,则该函数返回;如果未获得互斥对象的信号状态,则该函数会一直等待,线程会暂停;
dwMilliseconds:指定等待的时间间隔,单位为毫秒;如果指定的时间间隔已过,函数也会返回;如果设为0,该函数将测试该对象的状态并立即返回;如果设为INFINITE,该函数会一直等到互斥对象的信号才会返回;
返回值:
WAIT_OBJECT_0 --所请求的对象是有信号状态;
WAIT_TIMEOUT --指定的时间间隔已过,所请求的对象无信号;
WAIT_ABANDONED --所请求的对象是一个互斥对象,先前拥有该对象的线程在终止前未释放该对象,将该对象的所有权授予当前调用线程,并将该互斥对象设为无信号状态;
3)释放指定对象的所有权
BOOL ReleaseMutex( HANDLE hMutex );
hMutex:需要释放的互斥对象的句柄;
返回值:函数调用成功返回非0值,调用失败返回0;
注意:互斥对象谁拥有谁释放,请求几次就需要相应地释放几次;
#include <WINDOWS.H> #include <iosTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); //线程1的入口函数声明 DWORD WINAPI Fun2Proc(LPVOID lpParameter); //线程2的入口函数声明 int index=0; int tickets=100; HANDLE hMutex; void main() { HANDLE hThread1; HANDLE hThread2; //创建互斥对象 hMutex=CreateMutex(NULL,FALSE,NULL); //创建线程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); } //线程1的入口函数 DWORD WINAPI Fun1Proc(LPVOID lpParameter){ while(1) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; }else{ break; } ReleaseMutex(hMutex); } return 0; } //线程2的入口函数 DWORD WINAPI Fun2Proc(LPVOID lpParameter){ while(1) { WaitForSingleObject(hMutex,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- <<endl; }else{ break; } ReleaseMutex(hMutex); } return 0; }
2.1.2利用命名互斥对象来保证程序只有一个实例运行
调用CreateMutex函数创建一个命名的互斥对象,再调用GetLastError函数判断其返回值,如果返回ERROR_ALREADY_EXISTS,则表明已存在该命名的互斥对象,因此判断已有该应用程序的一个实例在运行了;如果返回的不是ERROR_ALREADY_EXISTS,则表明这个互斥对象为新创建的,因而可以判断当前启动的是该应用程序的第一个实例运行;
void main() { HANDLE hThread1; HANDLE hThread2; //创建命名互斥对象 hMutex=CreateMutex(NULL,TRUE,"tickets"); if(hMutex){ if(ERROR_ALREADY_EXISTS == GetLastError()){ cout << "只可以运行一个程序实例" << endl; return; } } //创建线程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); WaitForSingleObject(hMutex,INFINITE); ReleaseMutex(hMutex); ReleaseMutex(hMutex); Sleep(4000); }
2.2事件对象
2.2.1利用事件对象实现线程同步
1)事件对象分两种类型:人工重置的事件对象、自动重置的事件对象;
当人工重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程,当线程等待到该对象的所有权之后,需调用ResetEvent函数手动地将该事件对象设置为无信号状态;当自动重置的事件对象得到通知时,等待该事件对象的线程中只有一个线程变为可调度线程,当线程等到该对象的所有权之后,系统会自动将该事件对象设置为无信号状态;
2)创建事件对象
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
lpEventAttributes:指向SECURITY_ATTRIBUTES结构体的指针,如果设为NULL,则使用默认的安全性;
bManualReset:如果为TRUE,则创建一个人工重置的事件对象;如果为FALSE,则创建一个自动重置的事件对象;
bInitialState:指定事件对象的初始状态;如果为TRUE,那么该事件对象初始是有信号状态;如果为FALSE,初始是无信号状态;
lpName:指定事件对象的名称;如果为NULL,将创建一个匿名的事件对象;
3)设置事件对象状态
BOOL SetEvent(HANDLE hEvent); //将指定的事件对象设置为有信号的状态
hEvent:指定将要设置其状态的事件对象的句柄;
返回值:函数调用成功返回非0值,调用失败返回0;
4)重置事件对象状态
BOOL ResetEvent(HANDLE hEvent); //将指定的事件对象设置为无信号的状态
hEvent:指定将要重置其状态的事件对象的句柄;
返回值:函数调用成功返回非0值,调用失败返回0;
5)请求事件对象
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
#include <WINDOWS.H> #include <IOSTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); //线程1入口函数声明 DWORD WINAPI Fun2Proc(LPVOID lpParameter); //线程2入口函数声明 int tickets=100; HANDLE g_hEvent; void main() { HANDLE hThread1; HANDLE hThread2; //创建自动重置的事件对象 g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); SetEvent(g_hEvent); //将事件对象设为有信号状态 //创建线程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); //关闭事件对象句柄 CloseHandle(g_hEvent); } //线程1的入口函数 DWORD WINAPI Fun1Proc(LPVOID lpParameter) { while(1) { //请求事件对象 WaitForSingleObject(g_hEvent,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; //当线程请求到自动重置的事件对象后会将该事件对象设置为无信号状态 //在线程访问完成后须调用SetEvent函数将该事件对象设为有信号状态 SetEvent(g_hEvent); }else{ SetEvent(g_hEvent); break; } } return 0; } //线程2的入口函数 DWORD WINAPI Fun2Proc(LPVOID lpParameter) { while(1) { //请求事件对象 WaitForSingleObject(g_hEvent,INFINITE); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- << endl; SetEvent(g_hEvent); }else{ SetEvent(g_hEvent); break; } } return 0; }
2.2.2利用事件对象来保证程序只有一个实例运行
调用CreateEvent函数创建一个命名事件对象,再调用GetLastError函数判断其返回值,如果返回ERROR_ALREADY_EXISTS,则表明已存在该命名事件对象,因此判断已有该应用程序的一个实例在运行了;如果返回的不是ERROR_ALREADY_EXISTS,则表明这个事件对象为新创建的,因而可以判断当前启动的是该应用程序的第一个实例运行;
void main() { HANDLE hThread1; HANDLE hThread2; //创建自动重置的命名事件对象 g_hEvent=CreateEvent(NULL,FALSE,FALSE,"tickets"); if(g_hEvent){ if(ERROR_ALREADY_EXISTS == GetLastError()){ cout << "只可以运行一个程序实例" << endl; return; } } SetEvent(g_hEvent); //将事件对象设为有信号状态 //创建线程 hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); Sleep(4000); //关闭事件对象句柄 CloseHandle(g_hEvent); }
2.3关键代码段
2.3.1利用关键代码段实现线程同步
关键代码段--也称为临界区,工作在用户模式下;它是指一段代码,在代码能够执行前,它必须独占对某些资源的访问权;通常把多线程中访问同一种资源的那部分代码当作关键代码段;
1)创建关键代码段
VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
lpCriticalSection:指向CRITICAL_SECTION结构体的指针;作为返回值使用,在调用该函数前需构造一个CRITICAL_SECTION结构体类型的对象,然后将该对象地址传递给该函数;
2)卸载关键代码段
VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
3)获得关键代码段的所有权
VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
该函数等待指定的关键代码段的所有权,如果该所有权赋予了调用的线程,则函数返回;否则该函数会一直等待,导致线程等待;
4)释放关键代码段的所有权
VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection );
#include <WINDOWS.H> #include <IOSTREAM.H> DWORD WINAPI Fun1Proc(LPVOID lpParameter); DWORD WINAPI Fun2Proc(LPVOID lpParameter); int tickets=100; CRITICAL_SECTION g_cs; //将关键代码段对象定义为全局对象 void main() { HANDLE hThread1; HANDLE hThread2; hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL); hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL); CloseHandle(hThread1); CloseHandle(hThread2); //创建关键代码段 InitializeCriticalSection(&g_cs); Sleep(4000); //卸载关键代码段 DeleteCriticalSection(&g_cs); } DWORD WINAPI Fun1Proc(LPVOID lpParameter){ while(1) { //获得关键代码段的所有权 EnterCriticalSection(&g_cs); Sleep(1); if(tickets>0){ Sleep(1); cout << "Thread01 sell ticket:" << tickets-- << endl; //释放关键代码段的所有权 LeaveCriticalSection(&g_cs); }else{ LeaveCriticalSection(&g_cs); break; } } return 0; } DWORD WINAPI Fun2Proc(LPVOID lpParameter){ while(1) { //获得关键代码段的所有权 EnterCriticalSection(&g_cs); Sleep(1); if(tickets>0){ Sleep(1); cout << "Thread02 sell ticket:" << tickets-- << endl; //释放关键代码段的所有权 LeaveCriticalSection(&g_cs); }else{ LeaveCriticalSection(&g_cs); break; } } return 0; }
2.3.2在MFC中使用关键代码段
1.在类的构造函数中调用InitializeCriticalSection函数,在该类的析构函数中调用DeleteCriticalSection函数;
2.在程序中每次调用EnterCriticalSection函数获得访问关键代码段中资源的所有权,一定要相应地调用LeaveCriticalSection函数释放该所有权,否则其他等待的线程无法执行;
以上是关于系统API函数实现多线程及线程同步的主要内容,如果未能解决你的问题,请参考以下文章