VC++多线程全面讲解

使用 Spy++ 工具查看系统中当前的进程与线程!

1、创建新线程的三种方式:
方式一:CreateThread(记得关闭线程句柄)
使用情况:无MFC时使用。
方式二:AfxBeginThread(会自动释放的,不用你去释放)
使用情况:MFC中有界面的程序中可以用;可以使用工作线程和界面现场。
方式三:_beginthreadex(记得关闭线程句柄)
与操作系统相关的系统中使用

①、我们的程序到底是不是多线程运行的?有什么优点呢?
加个 Sleep(1000*10) 便知分晓,即对于一些比较耗时的操作,更建议使用多线程的方式!

多线程应用场景:如果主线程函数中有耗时的操作,就可以开一个线程来执行。

②、线程是否真的在执行呢?
调试打印方式判断:OutputDebugString(),输出Cstring信息。
可以输出到debugview软件中;

调试打印方式判断:OutputDebugString
int tipMsg = (int)lpParameter;
CString strTipMsg;
while(TRUE) {
strTipMsg.Format(_T("%d"), tipMsg++);
OutputDebugString(strTipMsg);
Sleep(50);
}

===================================================
③、线程函数调用主对话框类的成员:线程函数的参数起着决定性作用!

主线程对话框的类的指针是this,把this作为指针传进去线程函数。在线程函数中使用指针来调用主对话框的变量。

④、类的成员函数做为线程函数:https://www.cctry.com/thread-19591-1-1.html
把线程函数在类中声明为static函数才能作为线程函数。
但要使用类成员函数和变量,依然要把this作为变量传进去。


①、线程的挂起与恢复:SuspendThread()、ResumeThread()
在线程创建并运行后,用户可以对线程执行挂起和恢复操作,
挂起就是指暂停线程的执行,当然有暂停就有恢复,之后,用户可以通过指定的操作来恢复线程的正常执行!
※※※ 注意:线程的挂起与恢复是有次数的,即:可以多次挂起,但是之后想进行线程的正常执行必须进行多次恢复操作!

②、线程的优先级:
线程的相对优先级有以下取值:
THREAD_PRIORITY_TIME_CRITICAL:Time-critical,关键时间(最高)
THREAD_PRIORITY_HIGHEST:Highest,最高(其实是“次高”)
THREAD_PRIORITY_ABOVE_NORMAL:Above normal,高于标准
THREAD_PRIORITY_NORMAL:Normal,标准
THREAD_PRIORITY_BELOW_NORMAL:Below normal,低于标准
THREAD_PRIORITY_LOWEST:Lowest,最低(其实是“次低”)
THREAD_PRIORITY_IDLE:Idle,空闲(最低)

AfxBeginThread 创建的线程可以直接在创建的时候指定,

而 CreateThread 需要创建之后指定!
获取:GetThreadPriority
设置:SetThreadPriority

UINT __cdecl ThreadProc1(LPVOID lpParameter)
{
     
	CStdioFile mFile;
	mFile.Open(_T("C:\\123.txt"), CFile::modeCreate | CFile::modeReadWrite);
	int tipMsg = (int)lpParameter;
	CString strTipMsg;
	while(TRUE) {
     
		strTipMsg.Format(_T("%d\r"), tipMsg);
		mFile.WriteString(strTipMsg);
	}

	mFile.Close();

	return 0;
}

UINT __cdecl ThreadProc2(LPVOID lpParameter)
{
     
	CStdioFile mFile;
	mFile.Open(_T("C:\\456.txt"), CFile::modeCreate | CFile::modeReadWrite);
	int tipMsg = (int)lpParameter;
	CString strTipMsg;
	while(TRUE) {
     
		strTipMsg.Format(_T("%d\r"), tipMsg);
		mFile.WriteString(strTipMsg);
	}

	mFile.Close();

	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	CWinThread *pThread = AfxBeginThread(ThreadProc1, (LPVOID)111, THREAD_PRIORITY_LOWEST);
	//SetThreadPriority(pThread->m_hThread, THREAD_PRIORITY_LOWEST);

	pThread = AfxBeginThread(ThreadProc2, (LPVOID)789, 
THREAD_PRIORITY_HIGHEST);
	//SetThreadPriority(pThread->m_hThread, THREAD_PRIORITY_HIGHEST);
}

③、线程的退出与终结:
1、最好的方式:让线程函数主动退出,或者 return;
可以保证线程函数里面对象的析构函数被调用,以及线程申请的相关空间被释放;

2、线程自己主动退出,可以调用 ExitThread(MFC中使用 AfxEndThread);
线程函数里面对象的析构函数不会被调用,线程申请的相关空间被释放;
所以,在C语言里面可以使用该函数退出线程,但在C++里面不建议,因为C++里面有类!

3、其他程序强行结束目标线程:可以调用 TerminateThread
此函数非常危险,被结束的线程不会得到任何通知,线程申请的相关空间也不会被释放!
所以,离他远点!

4、线程退出码的获取:GetExitCodeThread
前提:句柄有效,不被关闭!
传入多线程句柄。

vc++多线程篇[5]—线程间通信

①、最常用的方式:全局变量或者多个线程都能看到的一个东东
1、全局变量方式;
2、大家都能访问到的一个东东;
3、全局变量的声明方式;

int g_Num = 100;
UINT __cdecl ThreadWriteProc(LPVOID lpParameter)
{
     
	while(TRUE) {
     
		++g_Num;
		Sleep(50);
	}
	return 100;
}

UINT __cdecl ThreadReadProc(LPVOID lpParameter)
{
     
	CString strTipMsg;
	while(TRUE) {
     
		strTipMsg.Format(_T("%d"), g_Num);
		OutputDebugString(strTipMsg);
		Sleep(50);
	}

	return 100;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	CWinThread *pThread = AfxBeginThread(ThreadWriteProc, NULL);
	AfxBeginThread(ThreadReadProc, NULL);
}

②、发消息方式:PostThreadMessage

#define MY_THREAD_MSG (WM_USER+100)
UINT __cdecl ThreadWriteProc(LPVOID lpParameter)
{
     
	int nCount = 0;
	DWORD dwThreadReadID = (DWORD)lpParameter;
	while(TRUE) {
     
		PostThreadMessage(dwThreadReadID, MY_THREAD_MSG, nCount++, NULL);
		Sleep(50);
	}
	return 0;
}

UINT __cdecl ThreadPrintProc(LPVOID lpParameter)
{
     
	MSG msg = {
     0};
	while(GetMessage(&msg, 0, 0, 0))
	{
     
		switch(msg.message) {
     
			case MY_THREAD_MSG:
				{
     
					int nCount = (int)msg.wParam;
					CString strText;
					strText.Format(_T("%d"), nCount);
					OutputDebugString(strText);
				}
				break;
		}
	}

	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	CWinThread *pThreadPrint = AfxBeginThread(ThreadPrintProc, NULL);
	CWinThread *pThreadWrite = AfxBeginThread(ThreadWriteProc, (LPVOID)pThreadPrint->m_nThreadID);
}

③、与界面线程方面的联系!
1、创建界面线程的返回值 CWinThread 类型指针,就是新线程的指针;
2、在新界面线程中调用 AfxGetApp(); 获取到的是程序主线程的指针;

通过以上两种方法中的任意一种,将指针进行强制类型转换之后,可以轻松的实现线程间的通信!

3、对于 PostThreadMessage 方式的通信,界面线程同样适用,重载界面线程类的 PreTranslateMessage 即可!

vc++多线程篇—线程间的同步机制①

①、线程同步的必要性:

int g_Num = 0;
UINT __cdecl ThreadProc(LPVOID lpParameter)
{
     
	for (int idx = 0; idx < 100; ++idx) {
     
		g_Num = g_Num+1;
		CString strNum;
		strNum.Format(_T("%d"), g_Num);
		g_Num = g_Num-1;
	}
	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	for (int idx = 1; idx <= 50; ++idx) {
     
		AfxBeginThread(ThreadProc, NULL);
	}
}
void CThreadTestDlg::OnBnClickedPrintBtn()
{
     
	int realNum = g_Num;
}

///

CStringArray g_ArrString;
UINT __cdecl ThreadProc(LPVOID lpParameter)
{
     
	int startIdx = (int)lpParameter;
	for (int idx = startIdx; idx < startIdx+100; ++idx) {
     
		CString str;
		str.Format(_T("%d"), idx);
		g_ArrString.Add(str);
	}
	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	for (int idx = 1; idx <= 50; ++idx) {
     
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
}

void CThreadTestDlg::OnBnClickedPrintBtn()
{
     
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx = 0; idx < nCount; ++idx) {
     
		OutputDebugString(g_ArrString.GetAt(idx));
	}
}

②、原子互锁家族函数:
1、InterlockedIncrement:加1操作;
2、InterlockedDecrement:减1操作;
3、InterlockedExchangeAdd:加上“指定”的值,可以加上一个负数;
4、InterlockedExchange、InterlockedExchangePointer:能够以原子操作的方式用第二个参数的值来取代第一个参数的值;

其他互锁家族的函数大家也可以参考下MSDN,理解理解意思!
一般情况下,在多线程编程中如果对某一个变量的值进行改变的话,使用以上互锁函数确实比较方便,但有很多时候多线程间会操作更为复杂的东西
比如对一个结构的赋值、对链表的插入与删除 等等,以上互锁函数不能满足要求,所以要使用更为高级的多线程间的同步技术!

③、Critical Sections(关键代码段、关键区域、临界区域)

使用方法:
1、初始化:InitializeCriticalSection;
2、删除:DeleteCriticalSection;
3、进入:EnterCriticalSection(可能造成阻塞);
4、尝试进入:TryEnterCriticalSection(不会造成阻塞);
5、离开:LeaveCriticalSection;

固有特点(优点+缺点):
1、是一个用户模式的对象,不是系统核心对象;
2、因为不是核心对象,所以执行速度快,有效率;
3、因为不是核心对象,所以不能跨进程使用;
4、可以多次“进入”,但必须多次“退出”;
5、最好不要同时进入或等待多个 Critical Sections,容易造成死锁;(尽量使用单个)
6、无法检测到进入到 Critical Sections 里面的线程当前是否已经退出!(enter和leave之间尽量不要使用指向时间长的函数)

vc++多线程篇—线程间的同步机制②

①、Mutex(互斥器)
②、Semaphores(信号量)
③、Event Objects(事件)

//示例代码:
CStringArray g_ArrString;
UINT __cdecl ThreadProc(LPVOID lpParameter)
{
     
	int startIdx = (int)lpParameter;
	for (int idx = startIdx; idx < startIdx+100; ++idx) {
     
		CString str;
		str.Format(_T("%d"), idx);
		g_ArrString.Add(str);
	}
	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	for (int idx = 1; idx <= 50; ++idx) {
     
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
}

void CThreadTestDlg::OnBnClickedPrintBtn()
{
     
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx = 0; idx < nCount; ++idx) {
     
		OutputDebugString(g_ArrString.GetAt(idx));
	}
}

///

①、Mutex(互斥器)

使用方法:
1、创建一个互斥器:CreateMutex;
2、打开一个已经存在的互斥器:OpenMutex;
3、获得互斥器的拥有权:WaitForSingleObject、WaitForMultipleObjects 等一类等待的函数……(可能造成阻塞);
4、释放互斥器的拥有权:ReleaseMutex;
5、关闭互斥器:CloseHandle;

HANDLE ghMutex = NULL;
CStringArray g_ArrString;
UINT __cdecl ThreadProc(LPVOID lpParameter)
{
     
	int startIdx = (int)lpParameter;
	for (int idx = startIdx; idx < startIdx+100; ++idx) 
   {
     
		CString str;
		str.Format(_T("%d"), idx);
		
		DWORD dwWaitResult = WaitForSingleObject(ghMutex, INFINITE);
		switch (dwWaitResult)
		{
     
		case WAIT_ABANDONED://
		case WAIT_OBJECT_0:
			g_ArrString.Add(str);
			ReleaseMutex(ghMutex);//让互斥信号有信号
			break;
		}
		//g_ArrString.Add(str);
	}
	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
	ghMutex = CreateMutex(NULL, FALSE, NULL);
	for (int idx = 1; idx <= 50; ++idx) {
     
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
}

void CThreadTestDlg::OnBnClickedPrintBtn()
{
     
	CString strCount;
	INT_PTR nCount = g_ArrString.GetCount();
	strCount.Format(_T("%d"), nCount);
	MessageBox(strCount);

	for (INT_PTR idx = 0; idx < nCount; ++idx) {
     
		OutputDebugString(g_ArrString.GetAt(idx));
	}

	CloseHandle(ghMutex);//在关闭任务时,关闭互斥
}

※ 命名标准:Mutex 可以跨进程使用,所以其名称对整个系统而言是全局的,所以命名不要过于普通,类似:Mutex、Object 等。
最好想一些独一无二的名字等!

固有特点(优点+缺点):
1、是一个系统核心对象,所以有安全描述指针,用完了要 CloseHandle 关闭句柄,这些是内核对象的共同特征;
2、因为是核心对象,所以执行速度会比 Critical Sections 慢几乎100倍的时间(当然只是相比较而言);
3、因为是核心对象,而且可以命名,所以可以跨进程使用;
4、Mutex 使用正确的情况下不会发生死锁;
5、在“等待”一个 Mutex 的时候,可以指定“结束等待”的时间长度;
6、可以检测到当前拥有互斥器所有权的线程是否已经退出!Wait……函数会返回:WAIT_ABANDONED

②、Semaphores(信号量)

租车例子的比喻很恰当!

使用方法:
1、创建一个信号量:CreateSemaphore;
2、打开一个已经存在的信号量:OpenSemaphore;
3、获得信号量的一个占有权:WaitForSingleObject、WaitForMultipleObjects 等一类等待的函数……(可能造成阻塞);
4、释放信号量的占有权:ReleaseSemaphore;
5、关闭信号量:CloseHandle;

HANDLE ghSemaphore = NULL;
UINT __cdecl ThreadProc(LPVOID lpParameter)
{
     
	int startIdx = (int)lpParameter;
	CString strOut;
	while(TRUE) {
     
		
		DWORD dwWaitResult = WaitForSingleObject(ghSemaphore, 0);
		switch (dwWaitResult)
		{
     
		case WAIT_OBJECT_0:
			strOut.Format(_T("Thread %d: wait succeeded !"), GetCurrentThreadId());
			OutputDebugString(strOut);
			ReleaseSemaphore(ghSemaphore, 1, NULL);

			break;
		case WAIT_TIMEOUT: 
			strOut.Format(_T("Thread %d: wait timed out !"), GetCurrentThreadId());
			OutputDebugString(strOut);
			break; 
		}
	}
	return 0;
}

void CThreadTestDlg::OnBnClickedBtn()
{
     
    //第一步:
	ghSemaphore = CreateSemaphore(NULL, 10, 10, NULL);
	for (int idx = 1; idx <= 20; ++idx) 
   {
     
		AfxBeginThread(ThreadProc, (LPVOID)(idx*10));
	}
}

void CThreadTestDlg::OnBnClickedPrintBtn()
{
     
	CloseHandle(ghSemaphore);
}

※ 命名标准:Semaphores 可以跨进程使用,所以其名称对整个系统而言是全局的,所以命名不要过于普通,类似:Semaphore、Object 等。
最好想一些独一无二的名字等!

固有特点(优点+缺点):
1、是一个系统核心对象,所以有安全描述指针,用完了要 CloseHandle 关闭句柄,这些是内核对象的共同特征;
2、因为是核心对象,所以执行速度稍慢(当然只是相比较而言);
3、因为是核心对象,而且可以命名,所以可以跨进程使用;
4、Semaphore 使用正确的情况下不会发生死锁;
5、在“等待”一个 信号量 的时候,可以指定“结束等待”的时间长度;
6、非排他性的占有,跟 Critical Sections 和 Mutex 不同,这两种而言是排他性占有,
即:同一时间内只能有单一线程获得目标并拥有操作的权利,而 Semaphores 则不是这样,同一时间内可以有多个线程获得目标并操作!

所以,这里面问大家一个问题,如果还是用信号量的方式去做之前向 CStringArray 中添加节点的同步可以吗?
可以,把CreateSemaphore的参数2设置成1。

③、Event Objects(事件)

Event 方式是最具弹性的同步机制,因为他的状态完全由你去决定,不会像 Mutex 和 Semaphores 的状态会由类似:
WaitForSingleObject 一类的函数的调用而改变,所以你可以精确的告诉 Event 对象该做什么事?以及什么时候去做!

使用方法:
1、创建一个事件对象:CreateEvent;
2、打开一个已经存在的事件对象:OpenEvent;
3、获得事件的占有权:WaitForSingleObject 等函数(可能造成阻塞);
4、释放事件的占有权(设置为激发(有信号)状态,以让其他等待中的线程苏醒):SetEvent;
5、手动置为非激发(无信号)状态:ResetEvent
6、关闭事件对象句柄:CloseHandle;

固有特点(优点+缺点):
1、是一个系统核心对象,所以有安全描述指针,用完了要 CloseHandle 关闭句柄,这些是内核对象的共同特征;
2、因为是核心对象,所以执行速度稍慢(当然只是相比较而言);
3、因为是核心对象,而且可以命名,所以可以跨进程使用;
4、通常被用于 overlapped I/O 或被用来设计某些自定义的同步对象。

#include 
#include 
#define THREADCOUNT 4 

HANDLE ghGlobalWriteEvent; 
HANDLE ghReadEvents[THREADCOUNT];
DWORD WINAPI ThreadProc(LPVOID);

void CreateEventsAndThreads(void) 
{
     
    HANDLE hThread; 
    DWORD i, dwThreadID; 

    // Create a manual-reset event object. The master thread sets 
    // this to nonsignaled when it writes to the shared buffer. 

    ghGlobalWriteEvent = CreateEvent( 
        NULL,               // default security attributes
        TRUE,               // manual-reset event
        TRUE,               // initial state is signaled
        TEXT("WriteEvent")  // object name
        ); 

    if (ghGlobalWriteEvent == NULL) 
    {
      
        printf("CreateEvent failed (%d)\n", GetLastError());
        return;
    }
    else if ( GetLastError() == ERROR_ALREADY_EXISTS )
    {
     
        printf("Named event already exists.\n");
        return;
    }

    // Create multiple threads and an auto-reset event object 
    // for each thread. Each thread sets its event object to 
    // signaled when it is not reading from the shared buffer. 

    for(i = 0; i < THREADCOUNT; i++) 
    {
     
        // Create the auto-reset event
        ghReadEvents[i] = CreateEvent( 
            NULL,     // no security attributes
            FALSE,    // auto-reset event
            TRUE,     // initial state is signaled
            NULL);    // object not named

        if (ghReadEvents[i] == NULL) 
        {
     
            printf("CreateEvent failed (%d)\n", GetLastError());
            return;
        }

        hThread = CreateThread(NULL, 
            0, 
            ThreadProc, 
            &ghReadEvents[i],  // pass event handle
            0, 
            &dwThreadID); 

        if (hThread == NULL) 
        {
     
            printf("CreateThread failed (%d)\n", GetLastError());
            return;
        }
    }
}

void WriteToBuffer(VOID) 
{
     
    DWORD dwWaitResult, i; 

    // Reset ghGlobalWriteEvent to nonsignaled, to block readers
 
    if (! ResetEvent(ghGlobalWriteEvent) ) 
    {
      
        printf("ResetEvent failed (%d)\n", GetLastError());
        return;
    } 

    // Wait for all reading threads to finish reading

    dwWaitResult = WaitForMultipleObjects( 
        THREADCOUNT,   // number of handles in array
        ghReadEvents,  // array of read-event handles
        TRUE,          // wait until all are signaled
        INFINITE);     // indefinite wait

    switch (dwWaitResult) 
    {
     
        // All read-event objects were signaled
        case WAIT_OBJECT_0: 
            // TODO: Write to the shared buffer
            printf("Main thread writing to the shared buffer...\n");
            break;

        // An error occurred
        default: 
            printf("Wait error: %d\n", GetLastError()); 
            ExitProcess(0); 
    } 

    // Set ghGlobalWriteEvent to signaled

    if (! SetEvent(ghGlobalWriteEvent) ) 
    {
     
        printf("SetEvent failed (%d)\n", GetLastError());
        ExitProcess(0);
    }

    // Set all read events to signaled
    for(i = 0; i < THREADCOUNT; i++) 
        if (! SetEvent(ghReadEvents[i]) ) 
        {
      
            printf("SetEvent failed (%d)\n", GetLastError());
            return;
        } 
}

void CloseEvents()
{
     
    int i;

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(ghReadEvents[i]);

    CloseHandle(ghGlobalWriteEvent);
}

void main()
{
     
    int i;

    // TODO: Create the shared buffer

    // Create the events and THREADCOUNT threads to read from the buffer

    CreateEventsAndThreads();

    // Write to the buffer three times, just for test purposes

    for(i=0; i < 3; i++)
        WriteToBuffer();

    // Close the events

    CloseEvents();
}

DWORD WINAPI ThreadProc(LPVOID lpParam) 
{
     
    DWORD dwWaitResult;
    HANDLE hEvents[2]; 

    hEvents[0] = *(HANDLE*)lpParam;  // thread's read event
    hEvents[1] = ghGlobalWriteEvent; // global write event

    dwWaitResult = WaitForMultipleObjects( 
        2,            // number of handles in array
        hEvents,      // array of event handles
        TRUE,         // wait till all are signaled
        INFINITE);    // indefinite wait

    switch (dwWaitResult) 
    {
     
        // Both event objects were signaled
        case WAIT_OBJECT_0: 
            // TODO: Read from the shared buffer
            printf("Thread %d reading from buffer...\n", 
                   GetCurrentThreadId());
            break; 

        // An error occurred
        default: 
            printf("Wait error: %d\n", GetLastError()); 
            ExitThread(0); 
    }

    // Set the read event to signaled

    if (! SetEvent(hEvents[0]) ) 
    {
      
        printf("SetEvent failed (%d)\n", GetLastError());
        ExitThread(0);
    } 

    return 1;
}

你可能感兴趣的:(C++,多线程)