Linux与C++11多线程编程(学习笔记)
多線程編程與資源同步
在Windows下,主線程退出后,子線程也會被關閉;
在Linux下,主線程退出后,系統不會關閉子線程,這樣就產生了僵尸進程
3.2.1創建線程
-
Linux 線程的創建
#include <unistd.h> #include <stdio.h> #include <pthread.h> void* threadfunc(void* arg) {while (1){sleep(1);printf("I am a new thread!!!\n");}return NULL; }int main() {pthread_t threadid;pthread_create(&threadid, NULL,threadfunc, NULL);while (1){}return 0; } -
Windows CRT1提供的線程創建函數
#include <process.h> #include <stdio.h> unsigned int __stdcall threadfun(void* args) {while (true){printf("I am new thread!");}return 0; } int main(int argc,char* argv[]) {unsigned int threadid;_beginthreadex(0, 0, threadfun, 0, 0, &threadid);while (true)//不讓主線程退出{}return 0; } -
C++ 提供的std::thread類
#include <iostream> #include <thread>void threadproc1() {while (true){printf("I am aNew Thread!!");} }void threadproc2(int a,int b) {while (true){printf("I an Thread2");} }int main() {std::thread t1(threadproc1);std::thread t2(threadproc2, 1, 2);while (true){} }這種方法容易出錯,原因如下:
#include <iostream> #include <thread>void threadproc1() {while (true){printf("I am aNew Thread!!");} }void func() {std::thread t(threadproc1); } int main() {func();while (true){} }這段代碼實際上試運行不了的,在vs2019 release模式下結果:
在func函數調用完成后,func中的局部變量t被銷毀,而此時線程函數仍然在運行.所以使用std::thread創建線程必須保證線程函數運行期間,線程對象始終有效!!
當然,我們也可以使用detach方法解決這個問題,使線程函數和線程對象脫離
... void func() {std::thread t(threadproc1);t.detach(); } ...這樣就可以運行了,但是不推進這樣做,因為我們需要線程對象對線程進行管理
3.2.2線程ID
下面介紹一下Linux系統線程ID本質
-
Linux系統線程ID本質
在Linux系統中有三種方法可以獲取一個線程的ID
- 調用pthread_create函數時,可以通過第一個參數獲取線程ID
- 在需要獲取ID的線程中調用pthread_self函數獲取
- 通過系統調用獲取線程ID
其中,方法一和方法二獲取線程ID的結果都是一樣的,都是pthread_t類型
不同的進程可能有同樣的地址內存塊(共享內存),所以通過方法一和方法二獲取到的線程ID可能不是全系統唯一的,而方法三獲取的線程ID是全系統唯一的,就是LWP(輕量級進程)2
-
C++ 獲取線程ID的方法
#include <thread> #include <iostream> #include <sstream> void worker_thread_func() {while (true){} }int main() {//獲取線程t的idstd::thread t(worker_thread_func);std::thread::id worker_thread_id=t.get_id();std::cout<<worker_thread_id<<std::endl;//獲取主線程的idstd::thread::id main_thread_id = std::this_thread::get_id();std::cout<<main_thread_id<<std::endl;while (true){} }運行結果(ubuntu20):
3.2.3 等待線程結束
-
在Linux下等待線程結束
Linux 線程庫提供了pthread_join函數,用來等待某線程的退出并接收他的返回值
這種操作被稱為匯接(join)
Declared in: pthread.h static int pthread_join(pthread_t __th, void * *__thread_return)參數__th是需要等待的線程ID;參數__thread__return是輸出參數,用于接受被等待線程的退出碼,
可以再調用pthread_exit()時指定退出碼
Declared in: pthread.h static void pthread_exit(void *__retval)其中__retval可以通過__thread_return參數獲得
下面來展示一個實例,在程序啟動時開啟一個工作線程,工作線程將當前系統時間寫入一個文件后,主線程等待工作線程退出后,從文件中讀取時間,并將其輸出
#include <cstdio> #include <string> #include <pthread.h> #include <cstring> #define TIME_FILENAME "time.txt" void* fileThreadFunc(void* arg) {time_t now = time(nullptr);tm* t = localtime(&now);char timeStr[32]={0};snprintf(timeStr,32,"%04d/%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);FILE* fp = fopen(TIME_FILENAME,"w");if(fp == nullptr){printf("打開文件(time.txt)失敗\n");return nullptr;}size_t sizeToWrite=strlen(timeStr) +1;size_t ret = fwrite(timeStr,1,sizeToWrite,fp);if(ret != sizeToWrite){printf("寫入錯誤!\n");}fclose(fp);return nullptr; }int main() {pthread_t fileThreadID;int ret = pthread_create(&fileThreadID, nullptr,fileThreadFunc, nullptr);if(ret != 0){printf("創建線程失敗\n");return -1;}int* retval;pthread_join(fileThreadID,(void**)&retval);FILE* fp = fopen(TIME_FILENAME,"r");if(fp == nullptr){printf("打開文件失敗!\n");return -2;}char buf[32] = {0};int sizeRead = fread(buf,1,32,fp);if(sizeRead == 0){printf("讀取文件失敗");fclose(fp);return -3;}printf("Current time is: %s.\n",buf);fclose(fp);return 0;}執行結果如下:
-
Windows 等待線程結束
在Windows下我們可以使用WaitForSingleObject function (synchapi.h)和WaitForMultipleObjects function (synchapi.h)
-
C++11提供的等待線程結束的函數
#include <cstdio> #include <cstring> #include <thread> #define TIME_FILENAME "time.txt" void fileThreadFunc() {time_t now = time(nullptr);tm* t = localtime(&now);char timeStr[32]={0};snprintf(timeStr,32,"%04d/%02d %02d:%02d:%02d",t->tm_year+1900,t->tm_mon+1,t->tm_mday,t->tm_hour,t->tm_min,t->tm_sec);FILE* fp = fopen(TIME_FILENAME,"w");if(fp == nullptr){printf("打開文件(time.txt)失敗\n");return ;}size_t sizeToWrite=strlen(timeStr) +1;size_t ret = fwrite(timeStr,1,sizeToWrite,fp);if(ret != sizeToWrite){printf("寫入錯誤!\n");}fclose(fp);return; }int main() {std::thread t(fileThreadFunc);if(t.joinable()) t.join();FILE* fp = fopen(TIME_FILENAME,"r");if(fp == nullptr){printf("打開文件失敗!\n");return -2;}char buf[32] = {0};int sizeRead = fread(buf,1,32,fp);if(sizeRead == 0){printf("讀取文件失敗");fclose(fp);return -3;}printf("Current time is: %s.\n",buf);fclose(fp);return 0; }
3.4.3 C++11 對整型變量原子操作的支持
C++11 提供了對整形變量原子操作的支持
即std::atomic這是一個模板類型
Defined in header <atomic> template< class T > struct atomic;例子
#include <atomic> #include <iostream> int main() {std::atomic<int> value{1};value++;//自增,原子操作std::cout<<value<<std::endl; }C++ 線程同步對象
C++ 新標準中新增用于線程同步的std::mutex和std::condition_variable
3.7.1 std::mutex系列
這個系列類型的對象均提供了加鎖lock,嘗試加鎖trylock和解鎖unlock的方法
#include <iostream> #include <chrono> #include <thread> #include <mutex> int g_num =0 ; std::mutex g_num_mutex; void slow_increment(int id) {for (int i = 0; i < 10; ++i){g_num_mutex.lock();++g_num;std::cout<<id<<"->"<<g_num<<std::endl;g_num_mutex.unlock();}std::this_thread::sleep_for(std::chrono::seconds(1)); } int main() {std::thread t1(slow_increment,0);std::thread t2(slow_increment,1);t1.join();t2.join();return 0; }輸出:
3.7.2 std::shared_mutex
std::shared_mutex的底層實現是操作系統提供的讀寫鎖,也就是說,在有多個線程對共享資源讀且少許線程對共享資源寫的情況下,std::shared_mutex比std::mutex效率更高
shared_mutex 通常用于多個讀線程能同時訪問同一資源而不導致數據競爭,但只有一個寫線程能訪問的情形。
3.7.3 std::condition_variable
Member functions
| (destructor) | destructs the object (public member function) |
| operator=[deleted] | not copy-assignable (public member function) |
| Notification | |
| notify_one | notifies one waiting thread (public member function) |
| notify_all | notifies all waiting threads (public member function) |
| Waiting | |
| wait | blocks the current thread until the condition variable is woken up (public member function) |
| wait_for | blocks the current thread until the condition variable is woken up or after the specified timeout duration (public member function) |
| wait_until | blocks the current thread until the condition variable is woken up or until specified time point has been reached (public member function) |
| Native handle | |
| native_handle | returns the native handle (public member function) |
`
TCP網絡編程的基本流程
Linux與C++11多線程編程(學習筆記)
Linux select函數用法和原理
socket的阻塞模式和非阻塞模式(send和recv函數在阻塞和非阻塞模式下的表現)
connect函數在阻塞和非阻塞模式下的行為
獲取socket對應的接收緩沖區中的可讀數據量
CRT原先是指Microsoft開發的C Runtime Library(C語言運行時庫),用于操作系統的開發及運行。后來在此基礎上開發了C++ Runtime Library,所以現在CRT是指Microsoft開發的C/C++ Runtime Library。在VC的CRT/SRC目錄下,可以看到CRT的源碼,不僅有C的,也有C++的。CRT(Microsoft’s C/C++ Runtime Library)的一個真子集(主要是C++ Runtime Library)是一個符合(或至少是企圖符合)C++標準的C++庫。而Windows API(以及Windows的其他許多部分)都是在CRT的基礎上開發的。 ??
既然稱作輕量級進程,可見其本質仍然是進程,與普通進程相比,LWP與其它進程共享所有(或大部分)邏輯地址空間和系統資源,一個進程可以創建多個LWP,這樣它們共享大部分資源;LWP有它自己的進程標識符,并和其他進程有著父子關系;這是和類Unix操作系統的系統調用vfork()生成的進程一樣的。LWP由內核管理并像普通進程一樣被調度。Linux內核是支持LWP的典型例子。Linux內核在 2.0.x版本就已經實現了輕量進程,應用程序可以通過一個統一的clone()系統調用接口,*用不同的參數指定創建輕量進程還是普通進程,通過參數決定子進程和父進程共享的資源種類和數量,這樣就有了輕重之分*。在內核中, clone()調用經過參數傳遞和解釋后會調用do_fork(),這個核內函數同時也是fork()、vfork()系統調用的最終實現。*在大多數系統中,LWP與普通進程的區別也在于它只有一個最小的執行上下文和調度程序所需的統計信息,而這也是它之所以被稱為輕量級的原因。* 因為LWP之間共享它們的大部分資源,所以它在某些應用程序就不適用了;這個時候就要使用多個普通的進程了。例如,為了避免內存泄漏(a process can be replaced by another one)和實現特權分隔(processes can run under other credentials and have other permissions)。 ??
總結
以上是生活随笔為你收集整理的Linux与C++11多线程编程(学习笔记)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手把手写Demo系列之车道线检测
- 下一篇: C/C++ Linux 异步IO(AIO