c++11多線程編程

C++11的多線程編程方式

std::thread

在c++11的標(biāo)準(zhǔn)里,線程已經(jīng)由標(biāo)準(zhǔn)庫提供:std::thread,它起源于POSIX thread璃岳,因此在使用std::thread來做線程編程時耕腾,編譯需要帶上-lpthread。

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>

void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

int main()
{
            int n = 0;
            std::thread t1; // t1 is not a thread
            std::thread t2(f1, n + 1); // pass by value
            std::thread t3(f2, std::ref(n)); // pass by reference
            std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
            t2.join();
            t4.join();
            std::cout << "Final value of n is " << n << '\n';
}

future和promise

除了std::thread這種原始的多線程編程方式剃法,c++11還提供了future,promise這種語義的多線程編程方式,future以及promise能很好地處理需要線程之間傳遞數(shù)據(jù)以及同步的情況贷洲。

// promise example
#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int (std::future<int>& fut) {
    int x = fut.get();
    std::cout << "value: " << x << '\n';
}

int main ()
{
  std::promise<int> prom;                      // create promise

  std::future<int> fut = prom.get_future();    // engagement with future

  std::thread th1 (print_int, std::ref(fut));  // send future to new thread

  prom.set_value (10);                         // fulfill promise
                                             // (synchronizes with getting the future)
  th1.join();
  return 0;
}

packaged_task

packaged_task是用于將future和promise連接起來的模板類收厨,旨在減少使用future和promise語義編寫多線程程序時的代碼的冗余。

// packaged_task example
#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
  for (int i=from; i!=to; --i) {
  std::cout << i << '\n';
  std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Lift off!\n";
return from-to;
}

int main ()
{
  std::packaged_task<int(int,int)> tsk (countdown);   // set up packaged_task
  std::future<int> ret = tsk.get_future();            // get future

  std::thread th (std::move(tsk),10,0);   // spawn thread to count down from 10 to 0

  // ...

  int value = ret.get();                  // wait for the task to finish and get result

  std::cout << "The countdown lasted for " << value << " seconds.\n";

  th.join();

  return 0;
}

async

async提供了最簡單的并發(fā)編程方式优构,使用async進(jìn)行并行編程無需考慮線程和鎖诵叁,在調(diào)用async時,該任務(wù)應(yīng)該不包含有需要鎖保護(hù)的共享數(shù)據(jù)钦椭,而async會根據(jù)當(dāng)前cpu的core的使用情況來決定創(chuàng)建多少個thread來運(yùn)行該任務(wù)拧额。

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <future>

template <typename RAIter>
int parallel_sum(RAIter beg, RAIter end)
{
    auto len = end - beg;
    if(len < 1000)
        return std::accumulate(beg, end, 0);

    RAIter mid = beg + len/2;
    auto handle = std::async(std::launch::async,
                         parallel_sum<RAIter>, mid, end);
    int sum = parallel_sum(beg, mid);
    return sum + handle.get();
 }

int main()
{
     std::vector<int> v(10000, 1);
     std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
}

c++11控制并發(fā)邏輯的方式

future和promise

這種方式主要是通過這種語義實現(xiàn)程序上的多線程的并發(fā)邏輯的控制,具體參考上面的程序彪腔。

鎖與條件變量

鎖是一個經(jīng)典的概念侥锦,經(jīng)常被用于控制多線程訪問共享數(shù)據(jù)的邏輯,條件變量更是提供了同步多個線程的語義德挣。條件變量的必須和鎖——跟更確地是std::unique_lock<std::mutex>——配合使用恭垦,
這種配合使用的限制可以使得條件變量在一些平臺上的效率得到最大的優(yōu)化。

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;

void worker_thread()
{
     // Wait until main() sends data
     std::unique_lock<std::mutex> lk(m);
     cv.wait(lk, []{return ready;});

     // after the wait, we own the lock.
     std::cout << "Worker thread is processing data\n";
     data += " after processing";

     // Send data back to main()
     processed = true;
     std::cout << "Worker thread signals data processing completed\n";

     // Manual unlocking is done before notifying, to avoid waking up
     // the waiting thread only to block again (see notify_one for details)
     lk.unlock();
     cv.notify_one();
 }

int main()
{
    std::thread worker(worker_thread);

    data = "Example data";
        // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();

    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';

    worker.join();
}

std::lock_guard和std::unique_lock類似格嗅,但是前者在構(gòu)造的時候就會加鎖番挺,后者可以不用在構(gòu)造時就立即加鎖(默認(rèn)構(gòu)造不需要鎖),加鎖可以發(fā)生在其生命期間的任何時刻屯掖,另外后者也可以轉(zhuǎn)移其所有權(quán)到別的變量玄柏。
條件變量的notify方法能用于線程的喚醒,當(dāng)有notify發(fā)生時贴铜,其他處于waiting狀態(tài)的線程會嘗試獲取鎖粪摘,成功后判斷需要喚醒的條件(通過wait的第二個參數(shù)給出)是否為真,如果是真就喚醒阀湿,否則釋放鎖繼續(xù)waiting赶熟。

一個充分使用了c++11特性的線程池

#ifndef THREAD_POOL_H
#define THREAD_POOL_H

#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>

class ThreadPool {
public:
    ThreadPool(size_t);
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args)
       -> std::future<typename std::result_of<F(Args...)>::type>;
          ~ThreadPool();
           private:
           // need to keep track of threads so we can join them
           std::vector< std::thread > workers;
           // the task queue
           std::queue< std::function<void()> > tasks;

    // synchronization
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
    :   stop(false)
{
        for(size_t i = 0;i<threads;++i)
            workers.emplace_back(
                        [this]
            {
                for(;;)
                {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        this->condition.wait(lock,
                                             [this]{ return this->stop || !this->tasks.empty(); });
                        if(this->stop && this->tasks.empty())
                            return;
                        task = std::move(this->tasks.front());                                                                                                                                                               
                        task();
                    }
                }   
             );
}

// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
    -> std::future<typename std::result_of<F(Args...)>::type>
{
    using return_type = typename std::result_of<F(Args...)>::type;

    auto task = std::make_shared< std::packaged_task<return_type()> >(
                std::bind(std::forward<F>(f), std::forward<Args>(args)...)
                        );

    std::future<return_type> res = task->get_future();
    {
       std::unique_lock<std::mutex> lock(queue_mutex);

        // don't allow enqueueing after stopping the pool
       if(stop)
           throw std::runtime_error("enqueue on stopped ThreadPool");

       tasks.emplace([task](){ (*task)(); });
    }
    condition.notify_one();
    return res;
 }

// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}

#endif

該線程池主要是維護(hù)了一個任務(wù)的隊列,初始化好的線程不斷從該隊列中取出任務(wù)并執(zhí)行陷嘴,同時維護(hù)好隊列。

一些陷阱

std::vector<std::future<std::pair<std::string, std::string> > > results;
results.emplace_back(thread_pool->enqueue([](){return std::make_pair();}));

for (auto && res: results) {
    auto & ret = res.get();
    auto t = std::thread(somefunc, std::ref(ret.first), std::ref(ret.second));
    t.detach();
}

這里的問題在于ret.first, ret.second會在不同線程里使用同一個對象的引用间坐,其值在不同線程里都一樣灾挨,這明顯有悖初衷。修改的方法簡單直接:使用值傳遞竹宋,而不是使用引用劳澄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜈七,隨后出現(xiàn)的幾起案子秒拔,更是在濱河造成了極大的恐慌,老刑警劉巖飒硅,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砂缩,死亡現(xiàn)場離奇詭異作谚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)庵芭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門妹懒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人双吆,你說我怎么就攤上這事眨唬。” “怎么了好乐?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵匾竿,是天一觀的道長。 經(jīng)常有香客問我蔚万,道長岭妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任笛坦,我火速辦了婚禮区转,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘版扩。我一直安慰自己废离,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布礁芦。 她就那樣靜靜地躺著蜻韭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柿扣。 梳的紋絲不亂的頭發(fā)上肖方,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音未状,去河邊找鬼俯画。 笑死,一個胖子當(dāng)著我的面吹牛司草,可吹牛的內(nèi)容都是我干的艰垂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼埋虹,長吁一口氣:“原來是場噩夢啊……” “哼猜憎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搔课,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤胰柑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柬讨,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崩瓤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了姐浮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谷遂。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卖鲤,靈堂內(nèi)的尸體忽然破棺而出肾扰,到底是詐尸還是另有隱情,我是刑警寧澤蛋逾,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布集晚,位于F島的核電站,受9級特大地震影響区匣,放射性物質(zhì)發(fā)生泄漏偷拔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一亏钩、第九天 我趴在偏房一處隱蔽的房頂上張望莲绰。 院中可真熱鬧,春花似錦姑丑、人聲如沸蛤签。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽震肮。三九已至,卻和暖如春留拾,著一層夾襖步出監(jiān)牢的瞬間戳晌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工痴柔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沦偎,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓咳蔚,卻偏偏與公主長得像扛施,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屹篓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 接著上節(jié) condition_varible ,本節(jié)主要介紹future的內(nèi)容匙奴,練習(xí)代碼地址堆巧。本文參考http:/...
    jorion閱讀 14,760評論 1 5
  • 接著上節(jié) atomic,本節(jié)主要介紹condition_varible的內(nèi)容,練習(xí)代碼地址谍肤。本文參考http://...
    jorion閱讀 8,464評論 0 7
  • 本文根據(jù)眾多互聯(lián)網(wǎng)博客內(nèi)容整理后形成啦租,引用內(nèi)容的版權(quán)歸原始作者所有,僅限于學(xué)習(xí)研究使用荒揣,不得用于任何商業(yè)用途篷角。 互...
    深紅的眼眸閱讀 1,092評論 0 0
  • 本文主要是針對C++中多線程并發(fā)操作參見(cplusplus)進(jìn)行解釋,文章從下面幾個方面進(jìn)行學(xué)習(xí)系任,分別介紹多線程...
    jorion閱讀 9,790評論 0 10
  • 接著上節(jié) mutex恳蹲,本節(jié)主要介紹atomic的內(nèi)容,練習(xí)代碼地址俩滥。本文參考http://www.cplusplu...
    jorion閱讀 73,584評論 1 14