C++11学习笔记-----线程库std::thread
在以前,要想在C++程序中使用線程,需要調(diào)用操作系統(tǒng)提供的線程庫(kù),比如linux下的<pthread.h>。但畢竟是底層的C函數(shù)庫(kù),沒(méi)有什么抽象封裝可言,僅僅透露著一種簡(jiǎn)單,暴力美
C++11在語(yǔ)言級(jí)別上提供了線程的支持,不考慮性能的情況下可以完全代替操作系統(tǒng)的線程庫(kù),而且使用起來(lái)非常方便,為開發(fā)程序提供了很大的便利
Linux下的原生線程庫(kù)
pthread庫(kù)函數(shù)
創(chuàng)建線程采用pthread_create函數(shù),函數(shù)聲明如下
#include <pthread.h> /* * pid : 線程id,傳入pthread_t類型的指針,函數(shù)返回時(shí)會(huì)返回線程id* attr : 線程屬性* func : 線程調(diào)用的函數(shù)* arg : 給函數(shù)傳入的參數(shù)*/ int pthread_create(pthread_t* pid, const pthread_attr_t* attr, void*(*func)(void*), void* arg);可以發(fā)現(xiàn),創(chuàng)建線程時(shí)只能傳遞一個(gè)參數(shù)給線程函數(shù),所以如果想要給函數(shù)傳入多個(gè)參數(shù)的話就需要?jiǎng)狱c(diǎn)歪腦筋,比如如果是類對(duì)象的話可以傳入this指針,或者也可以將參數(shù)封裝成一個(gè)struct傳進(jìn)去。不過(guò)總感覺(jué)不太優(yōu)雅
當(dāng)一個(gè)進(jìn)程創(chuàng)建一個(gè)線程時(shí),雖然線程運(yùn)行在主進(jìn)程的內(nèi)存空間中,但是每個(gè)線程也有自己的私有空間(資源,如局部變量等)。當(dāng)程序正常退出時(shí),執(zhí)行者是希望線程的私有資源可以成功被操作系統(tǒng)回收(即資源回收),這就需要主進(jìn)程在退出之前顯示調(diào)用pthread_join函數(shù),該函數(shù)會(huì)等待參數(shù)id代表的線程退出,然后回收其資源,如果調(diào)用時(shí)目標(biāo)線程還沒(méi)有運(yùn)行結(jié)束,那么調(diào)用方(主進(jìn)程)會(huì)被阻塞
當(dāng)然,如果覺(jué)得這樣太麻煩,也可以使用pthread_detach函數(shù)主動(dòng)分離線程,這樣,當(dāng)線程運(yùn)行結(jié)束后會(huì)由操作系統(tǒng)自動(dòng)回收資源,不再需要主進(jìn)程操心
還有一個(gè)常用的api是pthread_exit,用于主動(dòng)結(jié)束當(dāng)前線程
示例:若干線程并發(fā)對(duì)一個(gè)數(shù)進(jìn)行自增操作
使用示例,創(chuàng)建10個(gè)線程對(duì)進(jìn)程全局變量n做加法,每個(gè)線程加10000次
#include <unistd.h> #include <pthread.h> #include <sys/types.h>#include <iostream>long long int n = 0; void *thread_func(void* arg) {for(int i = 0; i < 10000; ++i)++n;::pthread_exit(nullptr); }int main() {for(int i = 0; i < 10; ++i){pthread_t pid;::pthread_create(&pid, nullptr, thread_func, nullptr); ::pthread_detach(pid);}::sleep(1); //等待所有線程正常退出std::cout << n << std::endl;return 0; }當(dāng)然最后這個(gè)結(jié)果絕不可能是100000,要想保證正確性,需要互斥鎖協(xié)助。
C++11線程庫(kù)
簡(jiǎn)單介紹了posix原生線程的使用,一方面用于復(fù)習(xí),另一方面自然是為了引出主角。C++11引入線程庫(kù)std::thread,使得C++在語(yǔ)言級(jí)別上支持線程,雖然大家都說(shuō)性能不咋地,但是用起來(lái)自然是方便許多。突出的幾個(gè)特點(diǎn)有
- 支持lambda,創(chuàng)建線程可以傳入lambda作為執(zhí)行函數(shù),太方便了有木有~
- 支持任意多個(gè)參數(shù),由于C++模板支持可變參數(shù)列表,所以實(shí)現(xiàn)多參數(shù)傳遞還是蠻容易的
- 使用方便,各種函數(shù)都經(jīng)過(guò)了良好設(shè)計(jì),使用起來(lái)比posix不知道高到哪里去了
小插曲,介紹了這么多好處當(dāng)然也要吐槽一下,編譯C++11線程庫(kù)居然要手動(dòng)鏈接-lpthread庫(kù)….
創(chuàng)建線程的幾種方式
使用線程庫(kù)需要引入頭文件<thread>,有下面幾種方法創(chuàng)建線程
#include <iostream> #include <thread> #include <chrono> #include <functional>class ThreadTask { public:ThreadTask(int a, int b): a_(a), b_(b){ }void operator()(){std::cout << "hello " << std::this_thread::get_id() << std::endl;std::cout << "a + b = " << a_ + b_ << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "world " << std::this_thread::get_id() << std::endl;}private:int a_;int b_; };void func(int a) {std::cout << "hello " << std::this_thread::get_id() << std::endl;std::cout << a << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "world " << std::this_thread::get_id() << std::endl; }int main() {std::thread t1(func, 1); //接收一個(gè)函數(shù)指針和參數(shù)列表std::thread t2([]() {std::cout << "hello " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "world " << std::this_thread::get_id() << std::endl;}); //接收l(shuí)ambdaThreadTask task(1, 2); std::thread t3(task); //接收函數(shù)對(duì)象t1.join(); //t1.detach();t2.join(); //t2.detach();t3.join(); //t3.detach();//std::this_thread::sleep_for(std::chrono::seconds(1));return 0; }需要注意的是,std::thread對(duì)象是不允許拷貝的,拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符被指定為delete
線程的移動(dòng)語(yǔ)義
雖然不允許拷貝,但是std::thread是允許移動(dòng)的
int main() {std::thread t1([]() {std::cout << "hello " << std::this_thread::get_id() << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "world " << std::this_thread::get_id() << std::endl;});std::thread t2(std::move(t4)); //移動(dòng)構(gòu)造函數(shù)std::thread t3;t3 = std::move(t2); //移動(dòng)賦值運(yùn)算符t3.join();return 0; }示例:利用std::thread實(shí)現(xiàn)并行的accumulate函數(shù)
標(biāo)準(zhǔn)庫(kù)std::accumulate函數(shù)用于對(duì)給定區(qū)間的元素依次運(yùn)算,默認(rèn)是加法,使用示例
#include <iostream> #include <algorithm> #include <vector>using namespace std;int main() {vector<int> v{1, 3, 5, 7, 9, 12};/* 輸出37,即所有元素的和 */std::cout << std::accumulate(v.begin(), v.end(), 0) << std::endl;/* 輸出5,即所有元素異或的結(jié)果 */std::cout << std::accumulate(v.begin(), v.end(), 0, bit_xor<int>()) << std::endl;return 0; }下面利用std::thread實(shí)現(xiàn)并行accumulate,即利用多線程的優(yōu)勢(shì)同時(shí)計(jì)算不同區(qū)塊
首先是根據(jù)給定區(qū)間計(jì)算創(chuàng)建的線程個(gè)數(shù),std::thread標(biāo)準(zhǔn)庫(kù)中提供了hardware_concurrency()函數(shù),該函數(shù)返回當(dāng)前計(jì)算機(jī)支持的并發(fā)線程數(shù),通常是cpu核數(shù),如果值無(wú)法計(jì)算則返回0。
在此之前,最好規(guī)定每個(gè)線程最小計(jì)算的元素個(gè)數(shù),不然如果創(chuàng)建線程過(guò)多,導(dǎo)致每個(gè)線程計(jì)算的元素個(gè)數(shù)很少,那么創(chuàng)建線程帶來(lái)的開銷就會(huì)大于運(yùn)算開銷,得不償失。所以可以規(guī)定每個(gè)線程最少計(jì)算20個(gè)元素
template <class Distance> auto getThreadNums(Distance count) {auto avaThreadNums = std::thread::hardware_concurrency();auto minCalNums = 20;/* 將count向上取整到20的整數(shù)倍,計(jì)算最大需要多少個(gè)線程 */auto maxThreadNums = ((count + (minCalNums - 1)) & (~(minCalNums - 1))) / minCalNums;/* 選擇二者中最合適的那個(gè) */return avaThreadNums == 0 ? maxThreadNums : std::min(static_cast<int>(avaThreadNums), static_cast<int>(maxThreadNums)); }通過(guò)線程個(gè)數(shù),就可以計(jì)算每個(gè)小區(qū)間負(fù)責(zé)的元素個(gè)數(shù),從而將區(qū)間[front, last)拆分成若干個(gè)小區(qū)間
auto count = std::distance(first, last); int threadNums = getThreadNums(count); int blockSize = count / threadNums; //區(qū)間大小接下來(lái)的工作就是創(chuàng)建threadNums個(gè)線程,每個(gè)線程調(diào)用std::accumulate計(jì)算自己負(fù)責(zé)的區(qū)間結(jié)果,同時(shí)將結(jié)果保存,最后將每個(gè)區(qū)間的結(jié)果再求一次std::accumulate
template <class InputIt, class T> T parallel_accumulate(InputIt first, InputIt last, T init) {auto count = std::distance(first, last);int threadNums = getThreadNums(count);int blockSize = count / threadNums;std::vector<std::thread> threads;std::vector<T> results(threadNums);auto front = first;for(int i = 0; i < threadNums; ++i){auto back = front;if(i != threadNums - 1)std::advance(back, blockSize);elseback = last;threads.emplace_back([front, back, &results, i, init] { results[i] = std::accumulate(front, back, init); });front = back;}for(auto& th : threads)th.join();return std::accumulate(results.begin(), results.end(), init); }測(cè)試代碼為
#include <iostream> #include <algorithm> #include <vector> #include <thread> #include <future> #include <chrono> #include <functional> #include <random>#include "../../tinySTL/Profiler/profiler.h"/* parallel_accumulate的實(shí)現(xiàn) */ ...int main() {std::vector<long long int> v(100000000);std::random_device rd;std::generate(v.begin(), v.end(), [&rd]() { return rd() % 1000; });tinystl::Profiler::ProfilerInstance::start(); auto result = parallel_accumulate(v.begin(), v.end(), 0);tinystl::Profiler::ProfilerInstance::finish(); tinystl::Profiler::ProfilerInstance::dumpDuringTime(); std::cout << result << std::endl;tinystl::Profiler::ProfilerInstance::start(); result = std::accumulate(v.begin(), v.end(), 0);tinystl::Profiler::ProfilerInstance::finish(); tinystl::Profiler::ProfilerInstance::dumpDuringTime(); std::cout << result << std::endl;return 0; }輸出結(jié)果
g++ thread.cpp -o thread -std=c++14 -lpthread -g //編譯 ./thread //執(zhí)行 total 213.717 milliseconds -1584776879 total 776.649 milliseconds -1584776879雖然都溢出了,但是可以看出并行計(jì)算快很多
小結(jié)
C++11提供的線程庫(kù)使用起來(lái)比較方便,以前在學(xué)習(xí)多線程編程時(shí)一直使用的posix原生線程庫(kù),剛剛接觸C++11時(shí)感覺(jué)方便很多,后面會(huì)繼續(xù)學(xué)習(xí)互斥鎖,條件變量的使用。
總結(jié)
以上是生活随笔為你收集整理的C++11学习笔记-----线程库std::thread的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 每天一道LeetCode-----后缀表
- 下一篇: C++11学习笔记-----互斥量以及条