WINDOWS — 基於C/C++的執行緒操作詳解(一)
2020/11/28
為了瞭解WINDOWS下的執行緒API介面使用方法,首先得知道以下幾個知識點。
一.什麼是程序?
官方解釋:
狹義定義---程序是正在執行的程式的例項(an instance of a computer program that is being executed)。 廣義定義---程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元,在傳統的作業系統中,程序既是基本的分配單元,也是基本的執行單元。簡單解釋:
電腦上的一個程式一執行就是一個程序的建立,開啟工作管理員,裡面就可以看到各個程序的佔用記憶體,CPU,磁碟等資訊。
二.什麼是執行緒?
官方解釋:
執行緒(英語:thread)是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。
簡單解釋:
抽象一點來說,就等於是高鐵運做是一個程序,高鐵上有駕駛員,服務員,每個人做自己要做的事,他們就是執行緒。
簡單瞭解完程序和執行緒,現在開始來了解基於C語言的執行緒操作:
既然是對於執行緒有關的操作,首先我們得知道我們是在一個什麼樣的環境下編譯程式碼執行操作。
我使用的是Visual Studio,,x64 是 WIN64環境(Microsoft Windows作業系統的64位環境),x86就是WIN32環境。
瞭解完編譯環境,來看看幾個接下來要用到的幾個基本的變數型別。
HANDLE,DWORD, WORD,BYTE,LPVOID ,我們來一個一個解釋。
首先是HANDLE,什麼是HANDLE,可以在VS中選中HANDLE型別,F12檢視一下,可以找到如下定義: typedef void *HANDLE,這樣就很明顯了,HANDLE是一個無型別的指標,那麼我們要這麼一個無型別的指標來做什麼呢,其實在WIN32的幫助文件裡面,解釋的非常簡潔明瞭:"A variable that identifies an object; an indirect reference to an operating system resource."
翻譯來就是:一個識別物件的變數;對作業系統資源的間接呼叫。
HANDLE的中文意思是“控制代碼”,其實我們可以這樣理解更為簡單,HANDLE是一個幫你找到"指向作業系統資源的指標"的東西,這樣的好處是,我們不需要直接使用指向作業系統的指標去呼叫資源,這樣就防止了我們的摳腳操作對作業系統造成無法預料的破壞,而用一個HANDLE去間接呼叫,在安全範圍內保證你無法對作業系統造成破壞,但你又能有一定方法去使用作業系統的資源。就比如說,我們建立了一個執行緒,執行緒屬於作業系統中的一個資源,那麼建立好後我們怎麼去對執行緒進行操作呢,這時HANDLE的作用不就體現出來了。
然後是DWORD,其實到編譯器裡一檢視DWORD定義,就能知道WORD和BYTE的定義了
typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef unsigned short WORD;
這裡很清晰的可以看到 DWORD = unsigned long (無符號長整形)
BYTE = unsigned char (無符號整形)
WORD = unsigned short(無符號短整型)
LPVOID 型別:無型別指標,任何型別的指標都可以賦值給LPVOID型別的變數,然後使用時再轉回來。
可以傳任何型別的值。
現在已經具備基本執行緒操作知識了,接下來在VS裡面建立一個執行緒。
我們要用到 windows中的 CreateThread() 介面,來建立一個執行緒,我們說了,建立執行緒後肯定要對執行緒進行一系列操作,那麼我們要用到HANDLE變數去操作,所以在使用CreateThread()函式時,應該用一個HANDLE變數去指向它。
先不提這些,我們先把CreatThread()的引數弄明白,才能去使用。
這裡我做了個簡單的釋義:
1 HANDLE CreateThread( 2 LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD:執行緒安全相關的屬性,常置為NULL 3 SIZE_T dwStackSize,//initialstacksize:新執行緒的初始化棧的大小,可設定為0 4 LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction:被執行緒執行的回撥函式,也稱為執行緒函式 5 LPVOID lpParameter,//threadargument:傳入執行緒函式的引數,不需傳遞引數時為NULL 6 DWORD dwCreationFlags,//creationoption:控制執行緒建立的標誌 7 LPDWORD lpThreadId//threadidentifier:傳出引數,用於獲得執行緒ID,如果為NULL則不返回執行緒ID 8 ) 9 */
有人好奇新執行緒的初始化棧的大小設為0位怎麼樣,不會怎麼樣,因為設為0只是告訴它我們要建立一個預設大小的初始棧,而預設大小為1MB,也就是1024*1024個位元組.
一個執行緒需要具備什麼,當然需要一個執行的任務過程,不然空執行緒沒有任何意義,回撥函式就是該執行緒需要執行的東西。
回撥函式需要滿足什麼,必須包括一個LPVOID的引數,然後滿足WINAPI要求,所以建立回撥函式一般是如下格式:
DWORD WINAPI 函式名 (LPVOID 引數名){}
執行緒回撥函式一般必須是全域性函式(特殊情況下可以設定為類成員函式)
之前也說了要用一個HANDLE變數去操作執行緒,所以一般流程如下:
1. HANDLE operate_thread;
2. operate_thread = CreateThread(NULL,0,func,(LPVOID)argv_test,0,NULL);
之後,要對這個執行緒操作都會用到這個operate_thread,相當於萬能鑰匙。
接下來我們在C語言基礎上完完整整的建立一個執行緒吧。
(還有一個前提,因為都是基於WINDOWS作業系統上操作,所以需要包括標頭檔案<windows.h>,如果是Linux則是<Phtreads.h>)
1 #include<windows.h> 2 #include<iostream> 3 using namespace std; 4 5 DWORD WINAPI func(LPVOID lpParam) { //回撥函式 6 for (int i = 0; i < 10; i++) { 7 cout << "The sub-Thread running.." << endl; 8 Sleep(500); 9 } 10 return 0; 11 } 12 13 int main(int argc, const char **argv) { 14 int argv_test = 10;//傳給回撥函式的引數 15 HANDLE th = CreateThread(NULL, 0, func,(LPVOID)argv_test, 0, NULL);//建立控制代碼用來之後操作執行緒。 16 CloseHandle(th);//如果之後都用不到這個控制代碼,就直接釋放 17 for (int i = 0; i < 10; i++) { 18 cout << "main thread running.." << endl; 19 Sleep(500); 20 } 21 22 system("pause"); 23 return 0; 24 }
(注:main函式也算一個執行緒,算主執行緒)
這樣一個簡單的執行緒操作程式就完成了。