1. 程式人生 > >C++11學習筆記-----執行緒庫std::thread

C++11學習筆記-----執行緒庫std::thread

在以前,要想在C++程式中使用執行緒,需要呼叫作業系統提供的執行緒庫,比如linux下的<pthread.h>。但畢竟是底層的C函式庫,沒有什麼抽象封裝可言,僅僅透露著一種簡單,暴力美

C++11在語言級別上提供了執行緒的支援,不考慮效能的情況下可以完全代替作業系統的執行緒庫,而且使用起來非常方便,為開發程式提供了很大的便利

Linux下的原生執行緒庫

pthread庫函式

建立執行緒採用pthread_create函式,函式宣告如下

#include <pthread.h>
/* 
 * pid : 執行緒id,傳入pthread_t型別的指標,函式返回時會返回執行緒id
 * attr : 執行緒屬性
 * func : 執行緒呼叫的函式
 * arg : 給函式傳入的引數
 */
int pthread_create(pthread_t* pid, const pthread_attr_t* attr, void*(*func)(void*), void* arg);

可以發現,建立執行緒時只能傳遞一個引數給執行緒函式,所以如果想要給函式傳入多個引數的話就需要動點歪腦筋,比如如果是類物件的話可以傳入this指標,或者也可以將引數封裝成一個struct傳進去。不過總感覺不太優雅

當一個程序建立一個執行緒時,雖然執行緒執行在主程序的記憶體空間中,但是每個執行緒也有自己的私有空間(資源,如區域性變數等)。當程式正常退出時,執行者是希望執行緒的私有資源可以成功被作業系統回收(即資源回收),這就需要主程序在退出之前顯示呼叫pthread_join函式,該函式會等待引數id代表的執行緒退出,然後回收其資源,如果呼叫時目標執行緒還沒有執行結束,那麼呼叫方(主程序)會被阻塞

當然,如果覺得這樣太麻煩,也可以使用pthread_detach函式主動分離執行緒,這樣,當執行緒執行結束後會由作業系統自動回收資源,不再需要主程序操心

還有一個常用的api是pthread_exit,用於主動結束當前執行緒

示例:若干執行緒併發對一個數進行自增操作

使用示例,建立10個執行緒對程序全域性變數n做加法,每個執行緒加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; }

當然最後這個結果絕不可能是100000,要想保證正確性,需要互斥鎖協助。

C++11執行緒庫

簡單介紹了posix原生執行緒的使用,一方面用於複習,另一方面自然是為了引出主角。C++11引入執行緒庫std::thread,使得C++在語言級別上支援執行緒,雖然大家都說效能不咋地,但是用起來自然是方便許多。突出的幾個特點有

  • 支援lambda,建立執行緒可以傳入lambda作為執行函式,太方便了有木有~
  • 支援任意多個引數,由於C++模板支援可變引數列表,所以實現多引數傳遞還是蠻容易的
  • 使用方便,各種函式都經過了良好設計,使用起來比posix不知道高到哪裡去了

小插曲,介紹了這麼多好處當然也要吐槽一下,編譯C++11執行緒庫居然要手動連結-lpthread庫….

建立執行緒的幾種方式

使用執行緒庫需要引入標頭檔案<thread>,有下面幾種方法建立執行緒

#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);    //接收一個函式指標和引數列表
    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;
    });                         //接收lambda
    ThreadTask task(1, 2); 
    std::thread t3(task);       //接收函式物件

    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物件是不允許拷貝的,拷貝建構函式和拷貝賦值運算子被指定為delete

執行緒的移動語義

雖然不允許拷貝,但是std::thread是允許移動的

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));  //移動建構函式
    std::thread t3;
    t3 = std::move(t2); //移動賦值運算子

    t3.join();
    return 0;
}

示例:利用std::thread實現並行的accumulate函式

標準庫std::accumulate函式用於對給定區間的元素依次運算,預設是加法,使用示例

#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,即所有元素異或的結果 */
    std::cout << std::accumulate(v.begin(), v.end(), 0, bit_xor<int>()) << std::endl;
    return 0;
}

下面利用std::thread實現並行accumulate,即利用多執行緒的優勢同時計算不同區塊

首先是根據給定區間計算建立的執行緒個數,std::thread標準庫中提供了hardware_concurrency()函式,該函式返回當前計算機支援的併發執行緒數,通常是cpu核數,如果值無法計算則返回0。

在此之前,最好規定每個執行緒最小計算的元素個數,不然如果建立執行緒過多,導致每個執行緒計算的元素個數很少,那麼建立執行緒帶來的開銷就會大於運算開銷,得不償失。所以可以規定每個執行緒最少計算20個元素

template <class Distance>
auto getThreadNums(Distance count)
{
    auto avaThreadNums = std::thread::hardware_concurrency();
    auto minCalNums = 20;
    /* 將count向上取整到20的整數倍,計算最大需要多少個執行緒 */
    auto maxThreadNums = ((count + (minCalNums - 1)) & (~(minCalNums - 1))) / minCalNums;
    /* 選擇二者中最合適的那個 */
    return avaThreadNums == 0 ? maxThreadNums : std::min(static_cast<int>(avaThreadNums), static_cast<int>(maxThreadNums));
}

通過執行緒個數,就可以計算每個小區間負責的元素個數,從而將區間[front, last)拆分成若干個小區間

auto count = std::distance(first, last);
int threadNums = getThreadNums(count);
int blockSize = count / threadNums; //區間大小

接下來的工作就是建立threadNums個執行緒,每個執行緒呼叫std::accumulate計算自己負責的區間結果,同時將結果儲存,最後將每個區間的結果再求一次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);
        else
            back = 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);
}

測試程式碼為

#include <iostream>
#include <algorithm>
#include <vector>
#include <thread>
#include <future>
#include <chrono>
#include <functional>
#include <random>

#include "../../tinySTL/Profiler/profiler.h"


/* parallel_accumulate的實現 */
...

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;
}

輸出結果

g++ thread.cpp -o thread -std=c++14 -lpthread -g    //編譯
./thread    //執行
total 213.717 milliseconds
-1584776879
total 776.649 milliseconds
-1584776879

雖然都溢位了,但是可以看出平行計算快很多

小結

C++11提供的執行緒庫使用起來比較方便,以前在學習多執行緒程式設計時一直使用的posix原生執行緒庫,剛剛接觸C++11時感覺方便很多,後面會繼續學習互斥鎖,條件變數的使用。

相關推薦

C++11學習筆記-----執行std::thread

在以前,要想在C++程式中使用執行緒,需要呼叫作業系統提供的執行緒庫,比如linux下的<pthread.h>。但畢竟是底層的C函式庫,沒有什麼抽象封裝可言,僅僅透露著一種簡單,暴力美 C++11在語言級別上提供了執行緒的支援,不考慮效能的情況下可

基於C++11併發執行池與訊息佇列多執行框架——std::thread

1 前言  C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。    在之前我們主要使用的多執行緒庫要麼是屬於某個單獨平臺的,例如:POSIX執行緒庫(Linux),Windows

C++11學習筆記std::move和std::forward原始碼分析

std::move和std::forward是C++0x中新增的標準庫函式,分別用於實現移動語義和完美轉發。下面讓我們分析一下這兩個函式在gcc4.6中的具體實現。 預備知識 引用摺疊規則: X& + & => X& X&& +

[cpp].c++11學習筆記-std thread

std::thread用於啟動執行緒,可以用作跨平臺的執行緒庫。 它啟動執行緒的方式很靈活,可以支援C函式,類成員函式,類靜態函式等。 #include <thread> //標頭檔

使用C++11進行多執行歸併排序:std::thread

相對於使用pthread來說,c++的標準庫對多執行緒的程式設計封裝的非常好,使用起來有如下幾個優勢: 1:可以直接傳遞引數給函式,而不需要將它封裝到一個結構體再轉換成為void*傳入。 2

C++11學習筆記-----獲取非同步操作執行結果

在多執行緒環境中,不管是傳遞lambda還是傳遞函式指標,再或者是傳遞函式物件給std::thread,都很難獲取執行函式返回值。在以前,只能將結果以引用的形式作為執行緒函式引數的一部分以此儲存返回值,但是仍然存在很大侷限性,甚至不太美觀。C++11引入的std

C++多執行std::thread

C++11,包含標頭檔案 thread.h,並使用名稱空間std。 thread類提供的方法 方法 描述 thread 建構函式,在這裡傳入執行緒執行函式,和函式引數

Linux 學習筆記執行間通訊的概念和執行控制函式

1 執行緒間通訊 執行緒間無需特別的手段進行通訊,由於執行緒間能夠共享資料結構,也就是一個全域性變數能夠被兩個執行緒同一時候使用。只是要注意的是執行緒間須要做好同步,一般用mutex。執行緒間的通訊目的主要是用於執行緒同步,所以執行緒沒有像程序通訊中的用於資料交

Linux 學習筆記執行同步之互斥量與條件變數

執行緒同步(同步的意思是協同步調) 執行緒同步機制包括互斥,讀寫鎖以及條件變數等 3.2.1 互斥量(互斥鎖) **互斥量本質是一把鎖,在訪問公共資源前對互斥量設定(加鎖),確保同一時間只有一個執行緒訪問資料,在訪問完成後再釋放(解鎖)互斥量。**在互斥量加鎖之

Linux 學習筆記執行同步之讀寫鎖、自旋鎖、屏障

3.2.1 讀寫鎖 讀寫鎖和互斥體類似,不過讀寫鎖有更高的並行性,互斥體要麼是鎖住狀態,要麼是不加鎖狀態,而且一次只有一個執行緒可以對其加鎖。而讀寫鎖可以有3個狀態,讀模式下鎖住狀態,寫模式下鎖住狀態,不加鎖狀態。一次只有一個執行緒可以佔有寫模式的讀寫鎖,但是多

TBSchedule原始碼學習筆記-執行組任務排程

根據上文的啟動過程,找到了執行緒組的實現。com.taobao.pamirs.schedule.taskmanager.TBScheduleManager /** * 1、任務排程分配器的目標: 讓所有的任務不重複,不遺漏的被快速處理。 * 2、

C++ 11 三個執行列印ABC(順序列印)

題目:有3個執行緒A,B, C, 請用多執行緒程式設計實現在螢幕上迴圈列印10次ABCABC..., 其中A執行緒列印“A”, B執行緒列印“B”, C執行緒列印“C”。 使用C++11 實做, 程式

c++11中多執行中Join函式

寫在前面 Join函式作用: Join thread The function returns when the thread execution has completed.//直到執行緒完成函式才返回 This synchronizes the moment t

C++11 學習筆記

筆記 c++11 使用 ++ har sha 不可 c++ 內存 unique_ptr 可以實現如下功能: 1、為動態申請的內存提供異常安全 2、講動態申請的內存所有權傳遞給某函數 3、從某個函數返回動態申請內存的所有權 4、在容器中保存指針 5、auto_ptr 應該具有

c++11開多執行

#include "iostream" #include "thread" #include "string" #include "functional" #include "mutex" using namespace std; std::mutex glock; c

python學習筆記 ---執行、程序、協程、佇列、python-memcache、python-redis

一、執行緒 Threading用於提供執行緒相關的操作,執行緒是應用程式中工作的最小單元。 #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time def show(arg): time.

c++11:多執行

    很高興c++11的標準庫可以#include <thread>了。boost早就提供了類似功能。這時候考慮下開發商、物業公司聯合阻礙成立業主委員會的藉口之一: 會妨礙事情的正常進展,推斷也許他們也是c++的長期使用者:) 1、pthread_xx的封裝

C++11:多執行與鎖

多執行緒是小型軟體開發必然的趨勢。C++11將多執行緒相關操作全部整合到標準庫中了,省去了某些坑庫的編譯,真是大大的方便了軟體開發。多執行緒這個庫簡單方便實用,下面給出簡單的例子 #include <iostream> #include <thread&

用BlockBoundQueue和c++11實現多執行生產者消費者問題

最近在讀到陳碩的《linux多執行緒服務端程式設計》這書時,發現了兩個特別好用的模板類 : BlockQueue和BlockBoundQueue,用來實現多執行緒中的生產者消費者問題是特別方便的。但是其原始碼中用到boost庫,所以在這裡我稍微修改下,實現如下。

APUE 學習筆記——執行控制

第12章 執行緒控制 1.  執行緒屬性 執行緒屬性結構體:pthread_arrr_t int pthread_attr_init(pthread_attr_t *attr);  //初始化執行緒屬性 int pthread_attr_destroy(pthread_a