MFC 多线程及线程同步
一、MFC对多线程编程的支持
MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。
在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:
(1) CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);//用于创建工作者线程
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:
UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
- pParam: 一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
- nPriority: 线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
- nStackSize: 线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
- dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
- lpSecurityAttrs:线程的安全属性指针,一般为NULL;
(2) CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。
下面我们对CWinThread类的数据成员及常用函数进行简要说明。
- m_hThread: 当前线程的句柄;
- m_nThreadID: 当前线程的ID;
- m_pMainWnd: 指向应用程序主窗口的指针
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。
一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。
virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。
二、MFC中线程同步
在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。
如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。
线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。
1.临界区
临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。
MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:
CCriticalSection g_clsCriticalSection; // MFC临界区类对象 char g_cArray[10]; // 共享资源 UINT ThreadProc20(LPVOID pParam) { g_clsCriticalSection.Lock(); // 进入临界区 for (int i = 0; i < 10; i++) // 对共享资源进行写入操作 { g_cArray[i] = 'a'; Sleep(1); } g_clsCriticalSection.Unlock(); // 离开临界区 return 0; } UINT ThreadProc21(LPVOID pParam) { g_clsCriticalSection.Lock(); for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } g_clsCriticalSection.Unlock(); return 0; } …… void CSample08View::OnCriticalSectionMfc() { AfxBeginThread(ThreadProc20, NULL); AfxBeginThread(ThreadProc21, NULL); Sleep(300); CString sResult = CString(g_cArray); AfxMessageBox(sResult); }
2.事件内核对象
在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:
HANDLE hEvent = NULL; // 事件句柄 char g_cArray[10]; // 共享资源 UINT ThreadProc12(LPVOID pParam) { WaitForSingleObject(hEvent, INFINITE); // 等待事件置位 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } SetEvent(hEvent); // 处理完成后即将事件对象置位 return 0; } UINT ThreadProc13(LPVOID pParam) { WaitForSingleObject(hEvent, INFINITE); for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } SetEvent(hEvent); return 0; } …… void CSample08View::OnEvent() { hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 创建事件 SetEvent(hEvent); // 事件置位 AfxBeginThread(ThreadProc12, NULL); // 启动线程 AfxBeginThread(ThreadProc13, NULL); Sleep(300); CString sResult = CString(g_cArray); AfxMessageBox(sResult); }
在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。
使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 指向事件对象名的指针
);
如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:
DWORD nCount, // 等待句柄数
CONST HANDLE *lpHandles, // 句柄数组首地址
BOOL fWaitAll, // 等待标志
DWORD dwMilliseconds // 等待时间间隔
);
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。 下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:
HANDLE hEvents[2]; // 存放事件句柄的数组 UINT ThreadProc14(LPVOID pParam) { DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); // 等待开启事件 if (dwRet1 == WAIT_OBJECT_0) // 如果开启事件到达则线程开始执行任务 { AfxMessageBox("线程开始工作!"); while (true) { for (int i = 0; i < 10000; i++); DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0); // 在任务处理过程中等待结束事件 if (dwRet2 == WAIT_OBJECT_0 + 1) // 如果结束事件置位则立即终止任务的执行 break; } } AfxMessageBox("线程退出!"); return 0; } …… void CSample08View::OnStartEvent() { for (int i = 0; i < 2; i++) // 创建线程 hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL); AfxBeginThread(ThreadProc14, NULL); // 开启线程 SetEvent(hEvents[0]); // 设置事件0(开启事件) } void CSample08View::OnEndevent() { SetEvent(hEvents[1]); // 设置事件1(结束事件) }
MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
3.信号量内核对象
信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
LONG lInitialCount, // 初始计数
LONG lMaximumCount, // 最大计数
LPCTSTR lpName // 对象名指针
);
参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 信号量名
);
在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:
HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 计数递增数量
LPLONG lpPreviousCount // 先前计数
);
该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。
信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:
HANDLE hSemaphore; // 信号量对象句柄 UINT ThreadProc15(LPVOID pParam) { WaitForSingleObject(hSemaphore, INFINITE); // 试图进入信号量关口 AfxMessageBox("线程一正在执行!"); // 线程任务处理 ReleaseSemaphore(hSemaphore, 1, NULL); // 释放信号量计数 return 0; } UINT ThreadProc16(LPVOID pParam) { WaitForSingleObject(hSemaphore, INFINITE); AfxMessageBox("线程二正在执行!"); ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } UINT ThreadProc17(LPVOID pParam) { WaitForSingleObject(hSemaphore, INFINITE); AfxMessageBox("线程三正在执行!"); ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } …… void CSample08View::OnSemaphore() { hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 创建信号量对象 AfxBeginThread(ThreadProc15, NULL); // 开启线程 AfxBeginThread(ThreadProc16, NULL); AfxBeginThread(ThreadProc17, NULL); }
在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:
// MFC信号量类对象 CSemaphore g_clsSemaphore(2, 2); UINT ThreadProc24(LPVOID pParam) { // 试图进入信号量关口 g_clsSemaphore.Lock(); // 线程任务处理 AfxMessageBox("线程一正在执行!"); // 释放信号量计数 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc25(LPVOID pParam) { // 试图进入信号量关口 g_clsSemaphore.Lock(); // 线程任务处理 AfxMessageBox("线程二正在执行!"); // 释放信号量计数 g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc26(LPVOID pParam) { // 试图进入信号量关口 g_clsSemaphore.Lock(); // 线程任务处理 AfxMessageBox("线程三正在执行!"); // 释放信号量计数 g_clsSemaphore.Unlock(); return 0; } …… void CSample08View::OnSemaphoreMfc() { // 开启线程 AfxBeginThread(ThreadProc24, NULL); AfxBeginThread(ThreadProc25, NULL); AfxBeginThread(ThreadProc26, NULL); }
互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。
以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为: HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
BOOL bInitialOwner, // 初始拥有者
LPCTSTR lpName // 互斥对象名
);
参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 互斥对象名
);
当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:
BOOL ReleaseMutex(HANDLE hMutex);
其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。
在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:
// 互斥对象 HANDLE hMutex = NULL; char g_cArray[10]; UINT ThreadProc18(LPVOID pParam) { // 等待互斥对象通知 WaitForSingleObject(hMutex, INFINITE); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[i] = 'a'; Sleep(1); } // 释放互斥对象 ReleaseMutex(hMutex); return 0; } UINT ThreadProc19(LPVOID pParam) { // 等待互斥对象通知 WaitForSingleObject(hMutex, INFINITE); // 对共享资源进行写入操作 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } // 释放互斥对象 ReleaseMutex(hMutex); return 0; } …… void CSample08View::OnMutex() { // 创建互斥对象 hMutex = CreateMutex(NULL, FALSE, NULL); // 启动线程 AfxBeginThread(ThreadProc18, NULL); AfxBeginThread(ThreadProc19, NULL); // 等待计算完毕 Sleep(300); // 报告计算结果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); }
互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
CMutex g_clsMutex(FALSE, NULL); // MFC互斥类对象 UINT ThreadProc27(LPVOID pParam) { g_clsMutex.Lock(); // 等待互斥对象通知 for (int i = 0; i < 10; i++) // 对共享资源进行写入操作 { g_cArray[i] = 'a'; Sleep(1); } g_clsMutex.Unlock(); // 释放互斥对象 return 0; } UINT ThreadProc28(LPVOID pParam) { g_clsMutex.Lock(); for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = 'b'; Sleep(1); } g_clsMutex.Unlock(); return 0; } …… void CSample08View::OnMutexMfc() { AfxBeginThread(ThreadProc27, NULL); AfxBeginThread(ThreadProc28, NULL); Sleep(300); CString sResult = CString(g_cArray); AfxMessageBox(sResult); }
转载于:https://blog.51cto.com/whatday/1382588
相关文章:

太火!这本AI图书微软强推,程序员靠它拿下50K!
如何能够短时间内抓住技术重点,集中突击?如何不在惧怕“算法”?如何才能在面试中对答如流,打造属于自己的“offer收割机”?也许这本书——《百面机器学习 算法工程师带你去面试》就能帮你实现!《百面机器学…

【Qt】错误处理:error: undefined reference to `qMain(int, char**)‘
1、问题描述 在一次编译Qt项目时,报错 H:\Qt\Qt5.14.2\5.14.2\mingw73_32\lib\libqtmain.a(qtmain_win.o):-1: In function `WinMain@16: C:\Users\qt\work\qt\qtbase\src\winmain\

Android WebView与ViewPager的滑动冲突分析
前言 如题所述,我使用的架构是ViewPagerFragmentWebView进行开发的,由于WebView的html页面代码是第三方的,这里不好放出来,所以只能放一个大致的架构图,如图所示,ViewPager包含了两个fragment,可以左右滑动…

关于outlook2010帐户设置
安装了office2010后,首次使用outlook,关于帐户设置,以qq邮件为例 开启imap服务2.打开outlook2010软件 由于有文字限制,其他的图解请链接http://wlinfang.blog.51cto.com/2961560/902193转载于:https://blog.51cto.com/wlinfang/90…

如何构建可解释的推荐系统?| 深度
作者 | gongyouliu来源 | 大数据与人工智能(ID:ai-big-data)推荐系统的目标是为用户推荐可能会感兴趣的标的物。通过算法推荐达到节省用户时间、提升用户满意度、为公司创造更多的商业价值的目的。要想达到这个目的就需要让用户信任你的推荐系…

【Qt】获取、比较Qt版本的宏和函数
1、版本号宏定义 版本号宏定义在QtCore\qconfig.h中,以Qt5.14.2为例 #define QT_VERSION_STR "5.14.2" #define QT_VERSION_MAJOR 5 #define QT_VERSION_MINOR 14 #

Spring Cloud企业微服务分布式云架构技术点整合
spring cloud本身提供的组件就很多,但我们需要按照企业的业务模式来定制企业所需要的通用架构...下面我针对于spring cloud微服务分布式云架构做了以下技术总结,希望可以帮助到大家:View: H5、Vue.js、Spring Tag、React、angular…

学习 JS navigator 对象
集合描述IEFOplugins[] 返回对文档中所有嵌入式对象的引用。 该集合是一个 Plugin 对象的数组,其中的元素代表浏览器已经安装的插件。Plug-in 对象提供的是有关插件的信息,其中包括它所支持的 MIME 类型的列表。 虽然 plugins[] 数组是由 IE 4 定义的&am…

【Ubuntu】解决ubuntu系统root用户下Chrome无法启动问题
1、问题描述 ubuntu系统在root用户下无法启动Chrome,报错: Running as root without --no-sandbox is not supported. See https://crbug.com/6381802、解决方法 如果是用命令行启动,启动时加上参数“–no-sandbox”即可; 如果…

程序媛眼中的程序猿原来是这样子的!
2019独角兽企业重金招聘Python工程师标准>>> 一直都想写一篇关于描述程序员的文章,但是一直没能开头,一来因为文笔不好,更主要的原因是貌似对程序员既熟悉又不熟悉,很怕写出来的是以偏概全,给大家造成对程序…

TinyMind人工智能社区5月热门技术文章排行榜TOP15
本文整理了TinyMind人工智能社区(https://www.tinymind.cn/)5月文章排行榜TOP15,欢迎大家阅读收藏。 1、谈谈Python那些不为人知的冷知识 https://www.tinymind.cn/articles/4158 此文章为系列文章,全集请看【Python冷知识】专辑 https://…

任意排列、组合终极Shell脚本
2019独角兽企业重金招聘Python工程师标准>>> 作者:crulat 永夜 #!/bin/bash # permutation_combination.sharg0-1 number${2} eval ary({1..${1}}) length${#ary[]} output(){ echo -n ${ary[${!i}]}; } prtcom(){ nsloop i 0 number1 output ${}; echo…

刚刚!我被产品小姐姐的笔记本深深吸引了....
今天我看到产品小姐姐的一个东西,我惊呆了,因为我好像看到了黑科技,事情是这个样子的....由于加班写代码,在比较疲劳的时候,就站起来随意的在办公室里走走,放松一下自己的脑神经。走到一个产品小姐姐身边&a…

【Qt】解决在linux上使用Qt的媒体模块(Qt += multimedia)缺少模块multimedia的问题
1、问题描述 在ubuntu上使用QAudioInput、QAudioOutput等音频相关的类时,报错。 2、解决方法 1)在pro工程文件中添加了 Qt multimedia 2)安装multimedia模组 sudo apt install libqt5multimedia* qtmultimedia5-*

设计模式之五 责任链模式(Chain of Responsibility)
2019独角兽企业重金招聘Python工程师标准>>> 一. 场景 相信我们都有过这样的经历; 我们去职能部门办理一个事情,先去了A部门,到了地方被告知这件事情由B部门处理; 当我们到了B部门的时候,又被告知这件事情已…

django 中文乱码或不识别
django是一个不错的WEB开源框架。今天测试,发现有些页面中文乱码,肯定是编码哪儿出了问题。 django配置要修改项目根目录的settings.py中的: TIME_ZONE America/Chicago LANGUAGE_CODE en-us 替换成: TIME_ZONE Asia/Shanghai …

【TX2】安装NVIDIA SDK Manager(JetPack 4.6)后,下载kernel和u-boot源码
1、先确认版本号 官网:L4T 32.6.1 is included as part of JetPack 4.6 2、下载源码 执行同步代码命令: ~/nvidia/nvidia_sdk/JetPack_4.6_Linux_JETSON_TX2_TARGETS/Linux_for_Tegra$ ./source_sync.sh 根据提示,输入1中查询到的版本号t…

忽略这一点,人工智能变人工智障!
作者 | The Economist译者 | 弯月,责编 | 伍杏玲出品 | CSDN(ID:CSDNnews)【编者按】当今社会,人工智能已进入我们生活的方方面面,很多人会担心算法的控制权过多,也有人担心数据有偏差的话&…
Loader 入门【Webpack Book 翻译】
原文链接:https://survivejs.com/webpack...翻译计划:https://segmentfault.com/a/11...附言:因为发现书中一些内容单独放出来会比较尴尬,所以会跳过部分章节,当然完整版会全部翻译,已经正在研究原版的网站…

backtrack X server 启动不了
本来安装好了啥事没有的。 自己硬想能不用打startx直接启动X server的 于是加了 if [ -z ... 的代码 到 ~/.xinitrc 结果X server就再也启动不了了 花了2个小时? 检索【inappropriate ioctl for device (25)】 有一篇文章提到了 xinitrc, 我才想起来好像是编辑了这个文件&…

【FFmpeg】windows下的库下载
1、官网 http://ffmpeg.org/ 2、点击下载,选择windows 左侧库下载,右侧源码下载 3、二进制库下载 官方网址:https://www.gyan.dev/ffmpeg/builds/#release-builds 4、gihub上的二进制镜像

19行关键代码,带你轻松入门PaddlePaddle单机训练
刚接触深度学习框架的同学可能会说新入手一个框架是不是会很难?NO,NO,NOPaddlePaddle的宗旨就是“easy to use!”PaddlePaddle是百度自研的集深度学习框架、工具组件和服务平台为一体的技术领先、功能完备的开源深度学习平台,有全…

用WinForm/WPF代码来为.NET Core 3.0功能投票
我们在5月报道过微软希望在.NET Core 3.0上运行WinForms和WPF。为了实现这个目标,他们正在构建一个新工具,该工具将允许你投票以决定他们需要把哪些API移植到.NET Core。但是,这不是一次直接进行的投票,而是基于你的应用程序正在使…

【FFmpeg】RTSP、RTMP相关命令及开源项目
一、RTSP转RTMP 海康摄像头:抓取 rtsp 流然后转换成 rtmp 推流出去 主码流 ffmpeg -i “rtsp://[用户名]:[密码]@192.168.1.100/h265/ch1/main/av_stream” -f flv -r 25 -s 1920x1080 -an rtmp://localhost/live/a ffmpeg -i “rtsp://[用户名]:[密码]@192.168.1.100:554/h2…

Day13 python基础---内置函数1
一,内置函数: 1.什么是内置函数 就是python给你提供的,拿来直接用的函数,比如print,input等等,截止到python版本3.6.2,现在pyhton一共为我们提供了69个内置函数。 2.作用域相关 # 作用域相关 ****** # globals() :返回…

百度大脑开放日第三期:四大全新平台、两大场景方案助力开发者逐梦 AI
人工智能正走在从技术攻坚到嵌入大众生活的历史进程中,这离不开千万开发者的助力,但开发者也需要“被赋能”。5 月 30 日,第三期百度大脑开放日在深圳微漾国际创客空间如期举行,再一次向开发者、行业人士展现了百度大脑的 AI 技术…

SQuirreL SQL Client 使用记录
如果您的工作要求您在一天之中连接许多不同的数据库 (oracle、DB2、mysql、postgresql、Sql Server等等),或者你经常需要在多个不同种类的数据库之间进行数导入导出。那么SQuirreL SQL Client 将会是比较理想的数据库客户端链接工具。 简单介…

使用Zabbix通过BMC管理口监控HP服务器
概述 本文的环境:Zabbix版本为3.4,一台Server,一台Porxy,一台agent。Porxy主动抓取agent的状态并sender到Server。 首先需要保证服务器的BMC口能够联网,并且拥有管理用户和密码,Proxy和agent能够保持联网。…

刚刚,百度宣布王海峰升任CTO
作者 | 夕颜、一一出品 | AI科技大本营(ID:rgznai100)导读:5 月 31 日,百度宣布,百度原高级副总裁王海峰升任百度 CTO,成为百度在组织大变革中一批“敢打硬仗”的代表人物得到晋升的典型。在百度人事动荡之…

【FFmpeg】结构体详解(一):AVCodec、AVCodecContext、AVCodecParserContext、AVFrame、AVFormatContext 、AVIOContext
FFmpeg结构体详解 一、FFmpeg中最关键的结构体之间的关系1、解协议(http,rtsp,rtmp,mms)2、解封装(flv,avi,rmvb,mp4)3、解码(h264,mpeg2,aac,mp3)4、存数据二、结构体详解1、AVCodec 是存储编解码器信息的结构体。1.1 enum AVMediaType type1.2 enum AVCodecID id1.3 co…