C++多线程创建与同步

C++多线程创建与同步

作者: yym439 时间: 2021-08-11

一、创建线程的四种方法

1.1 C++11 std:thread

1.2 windows api _beginthreadex/_endthreadex

uintptr_t _beginthreadex( void *security, //线程安全属性。
unsigned stack_size, //线程堆栈大小,可以为0。
unsigned ( *start_address )( void * ),//线程函数的入口地址。对于_beginthread,线程函数调用约定是_cdecl。对于_beginthreadex,线程函数调用约定是_stdcall。
void *arglist, //传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针,可以为NULL。
unsigned initflag, //线程创建的初始标志。为CREATE_SUSPENDED则挂起线程,使用ResumeThread激活线程,为0则立即执行。
unsigned *thrdaddr //线程Id。

);   
  1. _beginthreadex不会自动关闭线程句柄,需要使用CloseHandle现实的关闭句柄。所以_beginthreadex函数可以用WaitForSingleObject() 函数来获取线程对象来进行同步。

  2. _endthreadex显示式结束一个线程,线程函数返回时也会自动调用_endthreadex

1.3 windows api CreateThread/ExitThread

HANDLE WINAPI CreateThread(

_in LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向一个LPSECURITY_ATTRIBUTES结构的指针决定返回的句柄能否被继承,lpThreadAttributes空,不能被继承。

_in SIZE_T dwStackSize, //初始化的堆栈大小,以字节为单位。如果为0,使用默认的大小(1MB)。

_in LPTHREAD_START_ROUTINE lpStartAddress,   //函数的入口地址,是一个函数指针。 函数原型:DWORD WINAPI ThreadProc( [in]  LPVOID  lpParameter);

_in LPVOID lpParameter, //一个指针,被传递到线程函数里。

_in DWORD dwCreationFlags, //线程创建的标志。如果为CREATE_SUSPENDED,那么需要使用ResumeThread函数来激活线程函数,如果为0,线程函数立刻执行。

_out LPDWORD lpThreadId //一个指向线程id的指针,如果为空,线程id不被返回。

);
  1. 如果线程函数return,返回值会隐式调用ExitThread函数,可以使用GetExitCodeThread函数获得该线程函数的返回值

  2. 如果一个线程调用了CRT,应该使用_beginthreadex 和_endthreadex(需要使用多线程版的CRT)

1.4 MFC AfxBeginThread创建线程

CWinThread* AfxBeginThread(

CRuntimeClass* pThreadClass, //线程函数的入口地址。函数原型:UINT __cdecl MyControllingFunction( LPVOID pParam );
int nPriority = THREAD_PRIORITY_NORMAL, //线程优先级。  
UINT nStackSize = 0, //指明线程堆栈的大小,以字节为单位,可以为0。  
DWORD dwCreateFlags = 0, //线程创建标志。  
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程安全属性。  
);
  1. 可以调用AfxEndThread来终止线程或者return

二、退出线程方法

  1. 线程函数的return返回(最好),会自动调用退出线程函数
  2. 调用 _endthreadex()函数 或 ExitThread()函数(最好不要):
    • 如果使用这两种方法退出线程, 则不会执行线程函数的return语句, 所以就不会调用线程函数作用域内申请的类对象的析构函数, 会造成内存泄露.
    • 线程调用了一个子函数,如果子函数决定退出线程,return是没用的,_endthreadex即可终结线程
  3. std:thread创建的线程,只能自己实现退出线程逻辑;thread::detach()函数被调用后,执行的线程从线程对象中被分离,线程会继续执行。

    三、线程同步方法

    2.1. WaitForSingleObject函数

    DWORD WINAPI WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
    

函数功能: 等待函数 – 使线程进入等待状态,直到指定的内核对象被触发(等待CreateEvent有信号或线程结束被触发)

参数说明:

  • 第一个参数: 为要等待的内核对象(可以是线程句柄/CreateEvent)。
  • 第二个参数: 为最长等待的时间,以毫秒为单位,如传入5000就表示5秒,传入0就立即返回,传入INFINITE表示无限等待
  • 函数返回值: 在指定的时间内对象被触发,函数返回WAIT_OBJECT_0。超过最长等待时间对象仍未被触发返回WAIT_TIMEOUT。传入参数有错误将返回WAIT_FAILED

2.2. CreateEvent()

HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD   
BOOL bManualReset, // reset type   
BOOL bInitialState, // initial state   
LPCTSTR lpName // object name   
);  

函数功能: 创建一个Event同步对象,假设CreateEvent调用成功的话,会返回新生成的对象的句柄,否则返回NULL

参数说明:

  • lpEventAttributes 一般为NULL
  • bManualReset:
    • 假设true,人工复位,一旦该Event被设置为有信号,则它一直会等到ResetEvent()API被调用时才会恢复 为无信号。
    • 假设为false,主动复位,Event被设置为有信号,则当有一个wait到它的Thread时, 该Event就会自己主动复位,变成无信号;
  • bInitialState 初始状态,true,有信号,false无信号
  • lpName 事件对象的名称,可以为NULL

关联函数:

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

2.3. CreateMutex()创建互斥对象

HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
 BOOL bInitialOwner, // 初始拥有者
 LPCTSTR lpName // 互斥对象名
);
  • 函数功能: 创建互斥对象
  • 参数说明:
    • lpMutexAttributes一般位NULL
    • bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有
    • 创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄
  • 关联函数:
    • ReleaseMutex()释放其拥有的互斥对象
    • WaitForSingleObject函数等待互斥对象通知(Release后可能通知到),返回值位WAIT_ABANDONED_0

2.4. 临界区操作线程同步

>临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。 - 使用步骤:
- CRITICAL_SECTION g_cs; //临界区结构对象
- InitializeCriticalSection(&g_cs);  // 初始化临界区
- EnterCriticalSection(&g_cs); // 进入临界区
- LeaveCriticalSection(&g_cs); //离开临界区

2.5. CreateSemaphore()创建信号量

 信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目

HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
 LONG lInitialCount, // 初始计数
 LONG lMaximumCount, // 最大计数
 LPCTSTR lpName // 对象名指针
);
  • 使用步骤:
    • hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);// 创建信号量对象
    • WaitForSingleObject(hSemaphore, INFINITE);// 试图进入信号量关口
    • ReleaseSemaphore(hSemaphore, 1, NULL);// 释放信号量计数

三、参考博客

线程创建

线程加锁同步

线程加锁同步2

std:thread