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/