C++ 之多线程编程API

其实C++语言本身并没有提供多线程机制(当然目前C++ 11新特性中,已经可以使用std::thread来创建线程了,因为还没有系统地了解过,所以这里不提了。),但Windows系统为我们提供了相关API,我们可以使用他们来进行多线程编程。

1、线程的创建及销毁
(1)线程创建

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    SIZE_T dwStackSize,//initialstacksize:
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId
    )

创建一个线程,函数调用成功,返回线程句柄,否则返回NULL。
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。

dwStackSize:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。

lpStartAddress:指向线程函数的指针,函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。

lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。

dwCreationFlags:控制线程创建的标志,可取值如下:
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程(就绪状态),直到线程被唤醒时才调用
(2)0: 表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈的大小,如果STACK_SIZE_PARAM_IS_A_RESERVATION标志未指定,dwStackSize将会设为系统预留的值

lpThreadId:保存新线程的id

(2)关闭线程

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
VOID ExitThread(DWORD dwExitCode);
BOOL WINAPI CloseHandle(HANDLE hObject); 

TerminateThread函数只能由外部进行调用,关闭某线程,ExitThread函数线程自身主动调用,结束线程,以上两个函数一般配合CloseHandle函数一起使用。如果函数执行成功则返回true(非0),如果失败则返回false(0)
hThread: 欲结束线程的句柄
dwExitCode:退出码,一般置为NULL
HANDLE : 线程句柄

2、线程同步机制
(1)互斥量

/*互斥量创建*/
HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,       
    BOOL                  bInitialOwner,            
    LPCTSTR               lpName                  
);


MutexAttributes:也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。
bInitialOwner:表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex
lpName:Mutex的名称

/*等待互斥量*/
DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                            
    DWORD  dwMilliseconds              
);

WaitForSingleObject函数用来等待一个指定的对象(互斥量、临界区、事件、信号量),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。
hHandle:要等待的指定对象的句柄,及互斥量。
dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待直到dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。

BOOL WINAPI ReleaseMutex(HANDLE hMutex);

函数用来释放所拥有的互斥量锁对象,hMutex为释放的互斥量句柄。

#include 
#include 

using namespace std;

HANDLE hMutex;      /*定义全局互斥量*/
DWORD WINAPI ThreadProc2(LPVOID p)
{
    int i = 0;
    while(1)
    {
        WaitForSingleObject(hMutex,INFINITE);/*无限时间等待互斥量*/
        cout<<"线程2正在运行"</*释放互斥量*/
        Sleep(1000);/*睡眠1s,必不可少,将cpu交给其他线程*/
    }
    return 0;
}
DWORD WINAPI ThreadProc1(LPVOID p)
{
    int i = 0;
    while(i < 1)
    {
        WaitForSingleObject(hMutex,INFINITE); /*无限时间等待互斥量*/
        cout<<"线程1正在运行"</*释放互斥量*/
        Sleep(1000);/*睡眠1s,必不可少,将cpu交给其他线程*/
    }
    return 0;
}

int main(int argc, _TCHAR* argv[])
{
    HANDLE HThread;
    HANDLE HThread1;

    hMutex =  CreateMutex(NULL,false,false);/*初始化互斥量属性*/

    HThread = CreateThread(NULL,0,ThreadProc1,NULL,0,NULL);/*创建线程1*/

    HThread1 = CreateThread(NULL,0,ThreadProc2,NULL,0,NULL);/*创建线程2*/

    cin.get() ;

    return 0;
}

运行结果如下,可以看出线程已经实现同步。
C++ 之多线程编程API_第1张图片

(2)临界区

CRITICAL_SECTION cs;            //定义临界区对象
InitializeCriticalSection(&cs); //初始化临界区
EnterCriticalSection(&cs);     //进入临界区
LeaveCriticalSection(&cs);     //离开临界区
DeleteCriticalSection(&cs);    //删除临界区

(3)事件
一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle() 来关闭它,用SetEvent()来设置它使其有信号,用ResetEvent() 来使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待 其变为有信号。

/*创建事件对象*/
HANDLE  CreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,   
BOOLbManualReset,                           
BOOLbInitialState,                          
LPCTSTRlpName                               
);

lpEventAttributes[In]: 一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承
bManualReset[In]: 指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。
bInitialState[In]: 指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName[In]:指定事件的对象的名称,是一个以0结束的字符串指针。
返回值:如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,如果函数失败,函数返回值为NULL

/*等待事件对象,同WaitForSingleObject */
DWORD WaitForMultipleObjects(  
  DWORD nCount,             
  CONST HANDLE *lpHandles,  
  BOOL fWaitAll,            
  DWORD dwMilliseconds      
); 

nCount: 句柄的数量 最大值为MAXIMUM_WAIT_OBJECTS(64)
HANDLE: 句柄数组的指针。 HANDLE 类型可以为(Event,Mutex,Process,Thread,Semaphore )数组 。
BOOL bWaitAll: 等待的类型,如果为TRUE 则等待所有信号量有效在往下执行,FALSE 当有其中一个信号量有效时就向下执行。
DWORD dwMilliseconds: 超时时间 超时后向执行。 如果为WSA_INFINITE 永不超时。如果没有信号量就会在这死等。

/*设置事件Handle为有信号状态,即其他线程可以获取该事件*/
BOOL SetEvent(HANDLE Handle);

/*设置事件Handle为无信号状态,即其他线程无法获取该事件*/
BOOL  ResetEvent(HANDLE Handle

(3)信号量

//头文件
#include <windows.h>

//创建信号量API
HANDLE WINAPI CreateSemaphore(
 _In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//指向SECURITY_ATTRIBUTES的指针;
 _In_     LONG                  lInitialCount,        //信号量对象的初始值;
 _In_     LONG                  lMaximumCount,        //信号量对象的最大值,这个值必须大于0;
 _In_opt_ LPCTSTR               lpName                //信号量对象的名称;
);

//等待信号量API
DWORD WINAPI WaitForSingleObject(
  _In_ HANDLE hHandle,          //信号量对象句柄
  _In_ DWORD  dwMilliseconds    //等待信号量时间,INFINET代表永久等待;
);
返回值:
WAIT_ABANDONED(0x00000080L) 表示拥有信号量的线程再终止前未释放该信号量;
WAIT_OBJECT_0(0x00000000L)  表示等到了信号量;
WAIT_TIMEOUT(0x00000102L)   表示等待超时;
WAIT_FAILED((DWORD)0xFFFFFFFF)  表示该函数执行失败,用GetLastError()得到错误码;  

//释放信号量句柄
BOOL WINAPI ReleaseSemaphore(
  _In_      HANDLE hSemaphore,         //信号量对象句柄;
  _In_      LONG   lReleaseCount,      //信号量释放的值,必须大于0;
  _Out_opt_ LPLONG lpPreviousCount     //前一次信号量值的指针,不需要可置为空;
);
返回值:成功返回非0

3、线程间通信

/*发送消息*/
BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);

idThread:目标线程ID,可通过GetCurrentThreadId(),获取当前线程的ID
Msg:要发送的消息类型
wParam、lParam:消息内容

BOOL GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax);

lpMsg:读取到的消息内容
hWnd:获取消息的窗口句柄,无则置为NULL
wMsgFilterMin:消息的最低整数值
wMsgFilterMax:消息的最高整数值

#include "stdafx.h"
#include   
#include   
#include   
#include   
using namespace std;

#define UM_MSG1 WM_USER+1  
#define UM_MSG2 WM_USER+2  
DWORD WINAPI Thread1(LPVOID para)  
{  

    DWORD dwThreadId = *(DWORD *)para;  
    DWORD i=0;  
    char strTmp[100];  

    while(TRUE)  
    {  
        Sleep(1700);            
        sprintf(strTmp,"Hello %d ",i++);  
        PostThreadMessage(dwThreadId,UM_MSG1,(WPARAM)strTmp,(LPARAM)NULL);  

    }  

    return 0;     

}  
DWORD WINAPI Thread2(LPVOID para)  
{  

    char strTmp[100];  
    DWORD dwThreadId = *(DWORD *)para;  
    DWORD i=0;  
    while(TRUE)  
    {    
        Sleep(3000);  

        sprintf(strTmp,"World %d \n",i++);  
        PostThreadMessage(dwThreadId,UM_MSG2,(WPARAM)strTmp,(LPARAM)NULL);  

    }  

    return 0;  

}  
int main()  
{  

    DWORD dwValue =GetCurrentThreadId();  
    cout<<"GetCurrentThreadId "<0,&Thread1,&dwValue,0,NULL);  
    HANDLE hThread2 = CreateThread(NULL,0,&Thread2,&dwValue,0,NULL);  
    CloseHandle(hThread1);  
    CloseHandle(hThread2);  
    MSG msg;  
    while(GetMessage(&msg,NULL,0,0))  
    {  
        switch(msg.message)  
        {  
        case UM_MSG1:  
        case UM_MSG2:  
            printf("msg:%d: %s\n",msg.message,msg.wParam);  

            break;  
        default:  
            printf("Unknown msg:0x%x\n",msg.message);  
            break;  
        }  
        Sleep(1);  
    }  
    return 0;  
}   

运行结果如下,从中可以看出两个子线程一直在发消息给主线程:
C++ 之多线程编程API_第2张图片

总结:临界区是用户模式的线程同步,而事件、互斥量、信号量则是内核模式的线程同步,因此临界区相对于其他三种同步方式,减少用户态和内核态之间转换的过程,因此运行速度更快,但相对的适应性也差些。

你可能感兴趣的:(C++ 之多线程编程API)