線程的創建和基本使用


1. 什麼是線程? 爲什麼要用多線程?

線程,是操作系統能夠運行和計算和調度的最小單位。一個進程至少要包含一個線程,線程要包含在進程中,是進程實際運作單位。一個進程中可以多有個線程,每個線程併發執行。每個線程有獨立的棧空間,線程之間共享靜態存儲區和堆空間。

那麼什麼時候要用多線程呢? 當我們要使用併發的時候,就可以使用多線程。比如,我們要想要解碼音視頻的同時去播放音頻和視頻;打印機打印PDF並且界面顯示當前進度和提示信息等等。

2. 線程的創建和使用

(1) 使用操作系統API創建

無論是Windows還是Linux,都提供了創建線程的API提供給我們去創建線程。Windows中使用API CreateThread 創建一個線程。它的具體表現形式如下:

HANDLE
WINAPI
CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );
  • lpThreadAttributes , 是線程的安全屬性,一般設爲 nullptr
  • dwStackSize , 表示線程棧空間的大小,單位爲字節。一般設置爲0,表示默認大小。
  • lpStartAddress , 表示新線程所執行的函數指針,類型爲 LPTHREAD_START_ROUTINE 。其定義如下:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );
typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE;

即,一個參數 LPVOID 類型,返回值爲 DWORD 類型的函數指針。這裏需要指出的是,函數需要使用 WINAPI 修飾,也就是要使用 __stdcall 來修飾函數。

  • lpParameter ,傳給線程的參數,LPVOID 類型本質上是 void *
  • dwCreationFlags ,一般設置爲0,表示線程創建後立即執行。如果設置爲 CREATE_SUSPENDED ,表示使用 ResumeThread 時,線程纔開始執行。
  • lpThreadId ,輸出參數,返回線程ID。
  • 返回值: 返回線程句柄,如果失敗返回空(nullptr)。

下面是一個簡單的示例:

#include <stdlib.h>
#include <iostream>
#include <Windows.h>

// 線程中執行的函數
DWORD WINAPI threadProc(LPVOID lpParameters)
{
	bool isRunThread = true;
	while (isRunThread)
	{
		static int number = 0;
		if (number >= 5)
		{
			// 僅執行5次打印
			isRunThread = false;
			break;
		}
		std::cout << "This is Run in Thread ID:" << ::GetCurrentThreadId() <<  ", Number is :" << number++ << std::endl;

		Sleep(200);
	}
	
	return 0;
}

// Windows API 創建線程
HANDLE createWindowsThread(DWORD *threadId)
{
	HANDLE handle = ::CreateThread(nullptr, 0, threadProc, nullptr, 0, threadId);

	return handle;
}

int main(int argc, char** argv)
{
	DWORD threadId = 0;
	HANDLE threadHandle = createWindowsThread(&threadId);
	if (threadHandle)
	{
		// 線程創建成功,打印創建的線程ID和主線程ID
		std::cout << "Create Thread Success! Thread ID is " << threadId << std::endl;
		std::cout << "Main Thread ID is " << ::GetCurrentThreadId() << std::endl;

		// 等待線程結束
		::WaitForSingleObject(threadHandle, INFINITE);
	}
	else // 線程創建失敗
		std::cout << "Create Thread Error!" << std::endl;

	system("pause");
	return 0;
}

函數執行結果如下:
Create Thread Success! Thread ID is 162840
Main Thread ID is 9652
This is Run in Thread ID:16284, Number is :0
This is Run in Thread ID:16284, Number is :1
This is Run in Thread ID:16284, Number is :2
This is Run in Thread ID:16284, Number is :3
This is Run in Thread ID:16284, Number is :4

當然也有可能是創建的線程中先打印,後者同時交替着打印。
函數 createWindowsThread 中使用了Windows的創建線程API CreateThread ,線程中執行函數 threadProc ,while 循環中執行5次後線程退出。

  • GetCurrentThreadId 表示獲取當前所在線程的 線程ID
  • WaitForSingleObject 表示等待線程結束。這裏第一個參數爲線程的句柄,第二個參數用於表示等待的毫秒數,如果設置爲 INFINITE ,表示無限等待下去。

(2) 使用C++11創建

不同操作系統的創建線程的函數是不同的,比如windows中使用 CreateThread ,而Linux中使用 pthread_create 創建。對於跨平臺的開發,我們可以使用Qt等第三方開源庫完成跨平臺的線程創建,也可以使用C++11中提供的方法創建多線程。當前,使用的編譯器要支持C++11。

可以使用C++11提供的 std::thread 實現線程的創建。
我們改寫一下上面的例子,代碼如下:

#include <stdlib.h>
#include <iostream>
#include <thread>
#include <Windows.h>

// 線程中執行的函數
void threadProc(int count)
{
	bool isRunThread = true;
	while (isRunThread)
	{
		static int number = 0;
		if (number >= count)
		{
			// 僅執行Count次打印
			isRunThread = false;
			break;
		}
		std::cout << "This is Run in Thread ID:" << ::GetCurrentThreadId() << ", Number is :" << number++ << std::endl;

		Sleep(200);
	}
}

// C++11 創建線程
void createWindowsThread(void)
{
	std::thread thread(threadProc, 5);
	thread.detach();
}

int main(int argc, char** argv)
{
	createWindowsThread();

	system("pause");
	return 0;
}

程序運行結果如下:
This is Run in Thread ID:10568, Number is :0
This is Run in Thread ID:10568, Number is :1
This is Run in Thread ID:10568, Number is :2
This is Run in Thread ID:10568, Number is :3
This is Run in Thread ID:10568, Number is :4

這裏線程中執行的函數可以不指定爲 __stdcall 修飾。創建 std::thread 對象,構造函數中傳入線程中執行的函數指針和參數,如果沒有參數可以不指定。就可以完成線程的創建。

  • detach 表示線程與線程對象分離,線程資源的回收不受線程對象的控制。

如果想等待線程,可使用 join 函數,我們把代碼修改如下:

// C++11 創建線程
std::thread createWindowsThread(void)
{
	std::thread thread(threadProc, 5);
	return thread;
}

int main(int argc, char** argv)
{
	std::thread thread = createWindowsThread();
	if (thread.joinable())
		thread.join();

	std::cout << "Created Thread Finished!" << std::endl;

	system("pause");
	return 0;
}

執行結果如下:
This is Run in Thread ID:10568, Number is :0
This is Run in Thread ID:10568, Number is :1
This is Run in Thread ID:10568, Number is :2
This is Run in Thread ID:10568, Number is :3
This is Run in Thread ID:10568, Number is :4
Created Thread Finished!

這裏因爲 std::thread 實現了移動構造,所以當函數中的線程對象被消除的時候,線程的相關信息轉移給了新創建的線程函數。

  • join 函數,表示阻塞等待線程的結束,如果線程結束則繼續往下執行。joinable 方法可以判斷能否被 join ,在 join 前需要調用該方法。

(3) c++11線程ID的獲取

前面說了Windows的API獲取線程ID的方法,那麼如何使用C++11來獲取線程ID呢?

  • 可以使用 std::this_thread 類的 get_id 獲取當前線程的ID,這是一個靜態方法。
  • 也可以使用 std::thread 對象的 get_id 獲取線程ID。
    get_id 函數返回的爲一個 std::thread::id 的對象,無法直接通過該對象獲取線程ID的整數值,但是該對象重載了 << 方法,如果想獲取整數值需要藉助 std::ostringstream 實現。添加頭文件 #include <sstream>

具體實現代碼如下:

int stdThreadIdToInt(const std::thread::id& id)
{
	std::stringstream oss;
	oss << id;
	std::string string = oss.str();
	int threadId = atol(string.c_str());

	return threadId;
}

作者:douzhq
個人主頁:http://www.douzhq.cn
文章同步頁: http://www.douzhq.cn/thread_create/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章