• win32API多线程编程


    win32线程API

    在Windows平台下可以通过Windows的线程库来实现多线程编程。

    对于多线程程序可以使用Visual Studio调试工具进行调试,也可以使用多核芯片厂家的线程分析调试工具进行调试。

    Win32 API(了解Windows,代码小,效率高)

    • Windows操作系统为内核以及应用程序之间提供的接口
    • 将内核提供的功能进行函数封装
    • 应用程序通过调用相关的函数获得相应的系统功能

    _beginthread

    • _beginthread(函数名,栈大小,参数指针)
    • Win32 函数库中提供了操作多线程的函数, 包括创建线程、管理线程、终止线程、线程同步等接口。
      线程函数(线程开始执行的函数)
      DWORD WINAPI ThreadFunc (LPVOID
      lpvThreadParm );
      线程创建
      HANDLE CreateThread (
      LPSECURITY_ATTRIBUTES lpThreadAttributes,
      SIZE_T dwStackSize,
      LPTHREAD_START_ROUTINE lpStartAddress,
      LPVOID lpParameter,
      DWORD dwCreationFlags,
      LPDWORD lpThreadId );
    • 第一个参数lpThreadAtt,是一个指向SECURITY- ATTRIBUTES结构的指针,该结构制定了线程的安全属性,缺省为 NULL。
      第二个参数dwStackSize,是栈的大小,一般设置为0。
      第三个参数lpFun是新线程开始执行时,线程函数的入口地址。它必须是将要被新线程执行的函数地址,不能为NULL。
      第四个参数lpParameter,是线程函数定义的参数。可以通过这个参数传送值,包括指针或者NULL 。
      第五个参数dwCreationFlags,控制线程创建的附加标志,可以设置两种值。0表示线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用;
      第六个参数lpThreadID,为指向32位变量的指针,该参数接受所创建线程的ID号。如果创建成功则返回线程的ID,否则返回NULL。
    • CreateThread不会执行C运行时数据块, 因此在C运行时库的应用程序中,不能使用CreateThread创建线程,
      微软提供了另外的创建线程的方法:创建线程用process.h头文件中声明的C执行时期链接库函数_beginthread。
      语法:
      hThread = _beginthread (
      void( __cdecl *start_address )( void * ),
      unsigned stack_size, void *arglist) ;
      线程函数的语法:
      void __cdecl ThreadProc (void * pParam) ;
    #include "stdio.h"
    #include <windows.h>
    #include <process.h>
    #include <iostream>
    #include <fstream>
    using namespace std;
    
    //使用_beginthread函数创建线程的例子。
    
    void ThreadFunc1(PVOID param)
    {
    	while (1)
    	{
    		Sleep(1000);
    		cout << "  This is ThreadFunc1  " << endl;
    	}
    }
    void ThreadFunc2(PVOID param)
    {
    	while (1)
    	{
    		Sleep(1000);
    		cout << "  This is ThreadFunc2  " << endl;
    	}
    }
    
    //可以多次执行本程序,查看两个线程函数执行的顺序。
    
    int main()
    {
    	int i = 0;
    	_beginthread(ThreadFunc1, 0, NULL);
    	_beginthread(ThreadFunc2, 0, NULL);
    	Sleep(5000);
    	cout << "end" << endl;
    	return 0;
    }
    

    创建执行挂起终止

    • CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);

    • 安全属性 栈大小 线程函数 参数指针 附加标志 ID号(32位int指针)

    • 附加标志为0 创建即可执行

    • 附加标志为CREATE_SUSPENDED 创建就要挂起

    • ResumeThread(句柄) 挂起计数器-1,为0则进行

    • SuspendThread(句柄) 挂起计数器+1

    • TerminateThread(hand1, 1) 第二个参数为exitcode

    • 调用SetThreadPriority函数设置线程的相对优先级,例如
      Bool SetThreadPriority (HANDLE hPriority , int nPriority)
      参数hPriority 指向待设置的线程句柄

    • nPriority 是线程的相对优先级,可以是以下的值:
      空闲:THREAD - PRIORITY- IDLE 15
      最低线程:THREAD - PRIORITY- LOWEST 2
      低于正常线程:THREAD - PRIORITY- BELOW- NORMAL 1
      正常线程:THREAD - PRIORITY- NORMAL 0
      高于正常线程:THREAD - PRIORITY- ABOVE – NORMAL -1
      最高线程:THREAD - PRIORITY- HIGHEST -2
      关键时间:THREAD - PRIORITY- TIME – CRITICAL -15

    • 挂起与恢复函数原型
      DWORD SuspendThread(HANDLE hThread);
      挂起指定的线程(慎用,不处理同步对象)
      如果函数执行成功,则线程的执行被终止
      每次调用SuspendThread() 函数,线程将挂起计数器的值增1

      DWORD ResumeThread(HANDLE hThread);
      结束线程的挂起状态来执行这个线程
      每次调用ResumeThread() 函数,线程将挂起计数器的值减1
      若挂起计数器的值为0,则不会再减

    #include "stdio.h"
    #include <windows.h> 
    #include <iostream> 
    using namespace std;
    
    //简单的多线程创建、执行、挂起、终止的程序例子。
    
    DWORD WINAPI FunOne(LPVOID param) {
    	while (true)
    	{
    		Sleep(1000);
    		cout << "hello! ";
    		//cout<<"hello! "<<*((int*)param); 
    	}
    	return 0;
    }
    DWORD WINAPI FunTwo(LPVOID param) {
    	while (true)
    	{
    		Sleep(1000);
    		cout << "world! ";
    	}
    	return 0;
    }
    
    //注意创建线程函数的第五个参数的运用。
    //输入1和2数字,可以控制线程的启动和终止。
    //注意连续输入1和2数字线程的运行情况。
    //线程的终止函数。
    
    int main(int argc, char* argv[])
    {
    	int input = 0;
    
    	//创建线程。这里第四个参数值设为NULL也可以,因为线程函数里没有使用输入参数。
    	HANDLE hand1 = CreateThread(NULL, 0, FunOne, (void*)&input, CREATE_SUSPENDED, NULL);
    	HANDLE hand2 = CreateThread(NULL, 0, FunTwo, (void*)&input, CREATE_SUSPENDED, NULL);
    	while (true) {
    		cin >> input;
    		if (input == 1)
    		{
    			//恢复线程
    			ResumeThread(hand1);
    			ResumeThread(hand2);
    		}
    		if (input == 2)
    		{
    			//挂起线程
    			SuspendThread(hand1);
    			SuspendThread(hand2);
    		}
    		if (input == 0)
    		{
    			//终止线程
    			TerminateThread(hand1, 1);
    			TerminateThread(hand2, 1);
    		}
    		if (input == 9)
    			return 0;
    	};
    
    	return 0;
    
    }
    
    

    利用全局变量实现同步

    这样做可能会有一个问题,主线程结束时其他线程也就跟着结束了

    • 全局变量
      进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。
      int var; //全局变量
      UINT ThreadFunction ( LPVOID pParam {
      while (var)
      {
      …… //线程处理
      }
      return 0; }
      var是一个全局变量,任何线程均可以访问和修改。线程间可以利用此特性达到线程同步的目的。
    #include "stdio.h"
    #include <windows.h> 
    #include <iostream> 
    using namespace std;
    
    //使用全局变量同步线程的例子。
    
    //全局变量。
    int globalvar = false;
    
    DWORD WINAPI ThreadFunc(LPVOID pParam)
    {
    	cout << "  ThreadFunc  " << endl;
    	Sleep(100);
    
    	//修改全局变量的值。
    	globalvar = true;
    
    	return 0;
    }
    DWORD WINAPI ThreadFunc1(LPVOID pParam)
    {
    	while (1)
    		cout << "  ThreadFunc111  " << endl;
    	return 0;
    }
    DWORD WINAPI ThreadFunc2(LPVOID pParam)
    {
    	while (1)
    		cout << "  ThreadFunc222  " << endl;
    	return 0;
    }
    
    //这种方式可能存在一些问题。
    
    int main()
    {
    	HANDLE hthread1 = CreateThread(NULL, 0, ThreadFunc1, NULL, 0, NULL);
    	HANDLE hthread2 = CreateThread(NULL, 0, ThreadFunc2, NULL, 0, NULL);
    	HANDLE hthread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
    	if (!hthread)
    	{
    		cout << "Thread Create Error ! " << endl;
    		CloseHandle(hthread);
    	}
    	bool b =SetThreadPriority(hthread,15);
    	cout<<GetThreadPriority(hthread1)<<endl;
    	cout<<GetThreadPriority(hthread2)<<endl;
    	cout<<GetThreadPriority(hthread)<<endl;
    
    	//循环判断全局变量的值。
    	while (!globalvar)
    		cout << "Thread while" << endl;
    
    	cout << "Thread exit" << endl;
    	return 0;
    }
    
    
    Thread while  ThreadFunc111
      ThreadFunc111
      ThreadFunc111
      ThreadFunc111    ThreadFunc222
      ThreadFunc222
      ThreadFunc222
      ThreadFunc222
    Thread while
    Thread while
    Thread while
    Thread while
    
    
      ThreadFunc222
      ThreadFunc222
      ThreadFunc111  Thread while
    Thread while  ThreadFunc222
    
    
      ThreadFunc111
    Thread exit
      ThreadFunc111    ThreadFunc222
    
      ThreadFunc222    ThreadFunc111
      ThreadFunc111
      ThreadFunc222
      ThreadFunc111
    

    利用事件实现同步

    • 事件是WIN32提供的最灵活的线程间同步方式。
      事件存在两种状态:
      激发状态(signaled or true)
      未激发状态(unsignal or false)
      事件可分为两类:
      手动设置:这种对象只能用程序来手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。
      SetEvent只有一个参数,该参数指定了事件对象的句柄值,若事件成功激发,返回TRUE;
      ResetEvent函数将事件对象恢复到最初的非激发状态,只有一个参数,成功后返回真
      自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。

    • CreateEvent(NULL, FALSE, FALSE, NULL);

    • 第一个参数还是安全,默认为NULL,二参代表事件类型,true为手动清除信号,false为自动清除

    • 三参:事件的初始状态 四参:事件的名称

    • setEvent(句柄)

    • WaitForSingleObject(evRead, INFINITE);等待某个事件
      WaitForSingleObject (evFinish, INFINITE)
      参数1:等待对象
      参数2:等待时间
      返回值:WAIT_OBJECT_0(激发)
      WAIT_TIMEOUT(超时)
      WAIT_FAILED(错误)

    • WaitForMultipleObjects(2 ,evFin ,TRUE ,INFINITE)
      参数1:等待的句柄数
      参数2:等待的句柄数组
      参数3:确定是否等待所有句柄激发后才返回
      参数4:等待时间
      返回值: WAIT_OBJECT_I(激发事件在句柄数组中的索引)

    • 例子:有三个线程
      主线程、读线程ReadThread、写线程WriteThread
      读线程ReadThread必须在写线程WriteThread 的写操作完成之后才能进行读操作
      主线程必须在读线程ReadThread 的读操作完成后才结束
      定义两个事件对象evRead,evFinish
      evRead由写线程WriteThread用于通知读线程ReadThread 进行读操作
      evFinish由读线程ReadThread用于通知主线程读操作已经结束

    #include "stdio.h"
    #include <windows.h> 
    #include <iostream> 
    using namespace std;
    
    //**使用事件机制同步线程的例子。
    
    //两个事件。
    HANDLE evRead, evFin;
    HANDLE evWrite;
    
    void ReadThread(LPVOID param)
    {
    	//等待读事件。
    	WaitForSingleObject(evRead, INFINITE);
    
    	cout << "Reading" << endl;
    	//ResetEvent(evRead);//evRead为手动恢复类型时打开
    	SetEvent(evWrite);
    	WaitForSingleObject(evRead, INFINITE);
    	cout << "Reading11111" << endl;
    
    	//激活结束事件。
    	SetEvent(evFin);
    }
    void WriteThread(LPVOID param)
    {
    	cout << "Writing" << endl;
    
    	//激活读事件。
    	SetEvent(evRead);
    
    	WaitForSingleObject(evWrite, INFINITE);
    	cout << "Writing11111" << endl;
    	SetEvent(evRead);
    }
    int main(int argc, char* argv[])
    {
    	//创建两个事件,注意事件参数的含义。
    	evRead = CreateEvent(NULL, FALSE, FALSE, NULL);
    	evFin = CreateEvent(NULL, FALSE, FALSE, NULL);
    
    	evWrite = CreateEvent(NULL, FALSE, FALSE, NULL);
    	//evRead = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二个参数为TRUE
    	//evFin = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二个参数为TRUE
    	//evWrite = CreateEvent (NULL ,TRUE ,FALSE ,NULL) ;//修改第二个参数为TRUE
    
    	_beginthread(ReadThread, 0, NULL);
    	_beginthread(WriteThread, 0, NULL);
    
    	//等待结束事件。
    	WaitForSingleObject(evFin, INFINITE);
    
    	cout << "The Program is End" << endl;
    	return 0;
    }
    
    
    

    临界区

    • 防止多个线程同时执行一个特定代码段的机制
      适用于多个线程操作之间没有先后顺序,但要求互斥的同步
      多个线程访问同一个临界区的原则:
      一次最多只能一个线程停留在临界区内
      不能让一个线程无限地停留在临界区内,否则其它线程将不能进入该临界区
      定义临界区变量的方法如下:
      CRITICAL_SECTION gCriticalSection;
      通常情况下,CRITICAL_SECTION结构体应该被定义为全局变量,便于进程中的所有线程可以方便地按照变量名来引用该结构体。
    • 初始化临界区
      VOID WINAPI InitializeCriticalSection (
      LPCRITICAL_SECTION lpCriticalSection );
      删除临界区
      VOID WINAPI DeleteCriticalSection (
      LPCRITICAL_SECTION lpCriticalSection );
      进入临界区
      VOID WINAPI EnterCriticalSection (
      LPCRITICAL_SECTION lpCriticalSection );
      执行该语句时,程序会判断cs对象是否已被锁定,若没锁定则线程进入临界区,并将cs置为锁定状态;否则,线程线程被阻塞以等待cs解锁。
      离开临界区
      VOID WINAPI LeaveCriticalSection (
        LPCRITICAL_SECTION lpCriticalSection );
      线程执行该语句后,程序自动将cs解锁,并唤醒等待cs解锁的线程
    • 使用临界区编程的一般方法是:
      void WriteData()
      {
      EnterCriticalSection(&gCriticalSection);
      //do something
      LeaveCriticalSection(&gCriticalSection);
      }
      例子
      假如一个银行系统有两个线程执行取款任务,一个使用存折在柜台取款,一个使用银行卡在ATM取款。若不加控制,很可能账户余额不足两次取款的总额,但还可以把钱取走。
    #include "stdio.h"
    #include <windows.h> 
    #include <iostream> 
    #include <process.h>
    #include <iostream>
    #include <fstream>
    using namespace std;
    
    //**使用临界区机制同步线程。
    
    int total = 100;
    HANDLE evFin[2];
    CRITICAL_SECTION cs;//临界区。
    
    //可以去掉临界区机制,查看是否出现错误。(需要辅助sleep函数)
    void WithdrawThread1(LPVOID param)
    {
    	EnterCriticalSection(&cs);//进入临界区
    	if (total - 90 >= 0)
    	{//Sleep(100);
    		total -= 90;
    		cout << "You withdraw 90" << endl;
    	}
    	else
    		cout << "You do not have that much money" << endl;
    	LeaveCriticalSection(&cs);//退出临界区
    	SetEvent(evFin[0]);
    }
    void WithdrawThread2(LPVOID param)
    {
    	EnterCriticalSection(&cs);//进入临界区
    	if (total - 20 >= 0)
    	{
    		total -= 20;
    		cout << "You withdraw 20" << endl;
    	}
    	else
    		cout << "You do not have that much money" << endl;
    	LeaveCriticalSection(&cs);//退出临界区
    	//LeaveCriticalSection(&cs) ;
    	SetEvent(evFin[1]);
    }
    int main(int argc, char* argv[])
    {
    	evFin[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
    	evFin[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
    
    	InitializeCriticalSection(&cs);//初始化临界区。
    
    	_beginthread(WithdrawThread1, 0, NULL);//创建线程顺序影响执行的结果。
    	_beginthread(WithdrawThread2, 0, NULL);
    	//_beginthread(WithdrawThread1 , 0 , NULL) ;
    
    	int id = WaitForMultipleObjects(2, evFin, TRUE, INFINITE);//等待两个事件都激活。
    	cout << "句柄数组中的索引:" << id << endl;
    
    	DeleteCriticalSection(&cs);//删除临界区
    
    	cout << total << endl;
    	return 0;
    }
    
    

    先1后2

    先2后1

    互斥量

    • 通常用于协调多个线程或进程的活动,通过对资源“锁定”和“取消锁定”,来控制对共享资源的访问。
      当一个互斥量被一个线程锁定了,其他试图对其加锁的线程就会被阻塞;当对互斥量加锁的线程解除锁定后,则被阻塞的线程中的一个会得到互斥量。
      互斥量的作用是保证每次只能有一个线程获得互斥量
      使用CreateMutex函数创建:
      HANDLE CreateMutex(
      LPSECURITY_ATTRIBUTES lpMutexAttributes,
      BOOL bInitialOwner,
      LPCTSTR lpName
      );
      安全属性 是否手动清除(true为手动) 名称
    • 相关的API:
      CreateMutex 创建一个互斥对象,返回对象句柄;
      OpenMutex 打开并返回一个已存在的互斥对象的句柄,使之后续访问;
      ReleaseMutex 释放对互斥对象的占用,使之成为可用;
      使用互斥量的一般方法是:
      void Writedata()
      {
      WaitForSingleObject(hMutex,…);
      ...//do something
      ReleaseMutex(hMutex);
      }
    #include "stdio.h"
    #include <windows.h> 
    #include <iostream> 
    #include <process.h>
    #include <iostream>
    #include <fstream>
    using namespace std;
    
    //**互斥量的使用方法。
    
    #define THREAD_INSTANCE_NUMBER	3
    
    LONG g_fResourceInUse = FALSE;
    LONG g_lCounter = 0;
    
    DWORD ThreadProc(void* pData) {
    
    	int ThreadNumberTemp = (*(int*)pData);
    	HANDLE hMutex;
    
    	if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {
    		cout << "Open Mutex error!" << endl;
    	}
    
    	//cout << "ThreadProc is running hMutexxxxxxx!!"  << ThreadNumberTemp <<endl;
    	WaitForSingleObject(hMutex, INFINITE);//获取互斥量。
    	cout << "ThreadProc is running!!" << ThreadNumberTemp << endl;
    	cout << "ThreadProc gets the mutex-" << ThreadNumberTemp << endl;
    
    	ReleaseMutex(hMutex);//释放互斥量。
    	CloseHandle(hMutex);
    	return 0;
    }
    
    int main(int argc, char* argv[])
    {
    
    	int i;
    	DWORD ID[THREAD_INSTANCE_NUMBER];
    	HANDLE h[THREAD_INSTANCE_NUMBER];
    	HANDLE hMutex;
    	if ((hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, "Mutex.Test")) == NULL) {
    		if ((hMutex = CreateMutex(NULL, FALSE, "Mutex.Test")) == NULL) //注意第二个参数,当前线程是否拥有创建的锁
    		{
    			cout << "Create Mutex error!" << endl;
    			return 0;
    		}
    	}
    	//ReleaseMutex(hMutex);  //CreateMutex函数第二参数为TRUE时打开
    
    	//获取信号量的位置不同,将产生不同的结果。
       //WaitForSingleObject(hMutex,INFINITE);
    	for (i = 0; i < THREAD_INSTANCE_NUMBER; i++)
    	{
    		WaitForSingleObject(hMutex, INFINITE);//获取互斥量。本线程可重复获取,但解锁时需释放相同的次数。
    		h[i] = CreateThread(NULL,
    			0,
    			(LPTHREAD_START_ROUTINE)ThreadProc,
    			(void*)&ID[i],
    			0,
    			&(ID[i]));
    		//WaitForSingleObject(hMutex,INFINITE);//演示重复获取互斥量
    		if (h[i] == NULL)
    			cout << "CreateThread error" << ID[i] << endl;
    		else
    			cout << "CreateThread: " << ID[i] << endl;
    		ReleaseMutex(hMutex);
    		//Sleep(1000);
    	}
    	//ReleaseMutex(hMutex);//ReleaseMutex(hMutex);ReleaseMutex(hMutex);//演示释放重复获取的互斥量
    
    	WaitForMultipleObjects(THREAD_INSTANCE_NUMBER, h, TRUE, INFINITE);
    	cout << "Close the Mutex Handle! " << endl;
    	CloseHandle(hMutex);
    
    	return 0;
    }
    
    
    

    信号量

    • 信号量是一个核心对象,拥有一个计数器,可用来管理大量有限的系统资源
      当计数值大于零时,信号量为有信号状态
      当计数值为零时,信号量处于无信号状态
      创建信号量
      HANDLE CreateSemaphore (PSECURITY_ATTRIBUTE psa,
      LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);
      安全属性 初始数量 最大数量 信号量名称
    • 释放信号量
      BOOL WINAPI ReleaseSemaphore(HANDLE hSemaphore,
      LONG lReleaseCount, //信号量的当前资源数增加lReleaseCount
      LPLONG lpPreviousCount);
      打开信号量
      HANDLE OpenSemaphore (DWORD fdwAccess,
      BOOL bInherithandle, PCTSTR pszName );
    // exa7.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include <windows.h> 
    #include <iostream>
    using namespace std;
    
    //**使用信号量机制同步线程。
    
    #define THREAD_INSTANCE_NUMBER	3
    
    DWORD foo(void * pData) {
    
    	int ThreadNumberTemp = (*(int*) pData);
    	HANDLE hSemaphore;
    
    	if ((hSemaphore = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, "Semaphore.Test")) == NULL) {
    		cout << "Open Semaphore error!" << endl;
    	}
    
    	WaitForSingleObject(hSemaphore,INFINITE);//获取信号量。
    
    	cout << "foo is running!!!"  << ThreadNumberTemp << endl;
    	cout << "foo gets the semaphore-" << ThreadNumberTemp<< endl;
    	
    	ReleaseSemaphore(hSemaphore, 1, NULL);//释放一个单位的信号量
    
    	CloseHandle(hSemaphore);
    	return 0;
    }
    
    int main(int argc, char* argv[])
    {
    
    	int i;
    	DWORD ThreadID[THREAD_INSTANCE_NUMBER]; 
       	HANDLE hThread[THREAD_INSTANCE_NUMBER];
    	HANDLE hSemaphore;
    
    	
    	if ((hSemaphore = CreateSemaphore(NULL,1,1, "Semaphore.Test")) == NULL ) {
    		cout << "Create Semaphore error!" << endl;
    		return 0;
    	}	
    
    	//与互斥量一样,这里获取信号量的位置不同,会产生不同的结果。
    	for (i=0;i<THREAD_INSTANCE_NUMBER;i++)
    	{
    		WaitForSingleObject(hSemaphore,INFINITE);//获取信号量。不可重入。
    		hThread[i] = CreateThread(NULL, 
    							0,                          
    							(LPTHREAD_START_ROUTINE) foo, 
    							(void *)&ThreadID[i],                    
    							0,                       
    							&(ThreadID[i]));              
    		
    		if (hThread[i] == NULL)
    
    			cout << "CreateThread error" << ThreadID[i] << endl;
    		else
    			cout << "CreateThread: " << ThreadID[i] << endl;
    
    	    ReleaseSemaphore(hSemaphore, 1, NULL);
    	}
    
    	WaitForMultipleObjects(THREAD_INSTANCE_NUMBER,hThread,TRUE,INFINITE);
    	cout << "Close the Semaphore Handle! " << endl;
    	CloseHandle(hSemaphore);
    
    	return 0;
    }
    
    
  • 相关阅读:
    CocoaPods使用和在新工程中创建xcworkspace
    CocoaPods的安装使用和常见问题
    iMac上安装cocoapods步骤
    error:could not read data from info
    :after伪类+content内容生成
    无JavaScript实现选项卡轮转切换效果
    margin负值的相关应用
    absolute元素 text-align属性
    绝对定位元素的水平垂直居中
    flex 布局
  • 原文地址:https://www.cnblogs.com/Tony100K/p/11758936.html
Copyright © 2020-2023  润新知