C++併發編程 thread

std::thread

  C++11在標準庫中爲多線程提供組件, 使用線程須要包含頭文件 thread, 其命名空間爲 std.多線程

啓動新線程

每一個進程至少有一個線程: 執行main()函數的線程, 其他線程有其各自的入口函數(線程函數)。
當線程執行完線程函數後, 線程也會退出. 若是不傳入線程函數(相似這種形式std::thread t;), 線程不會運行. 線程函數不能重載, 不然不能編譯.
在爲一個線程建立了一個 std::thread 對象後, 若是線程已啓動(不傳入線程序函數時, 線程不會啓動), 必需要明確是加入(join)仍是分離線程(detach).函數

        // 啓動一個線程:
        void MyThread(const std::string& str)
        {
            PRINT_LINE_INFO();
            std::cout << str << std::endl;
        }
        //std::thread t(MyThread, "Hello C...");
        std::thread t([] {
            MyThread("Hello C...");
            MyThread("Hello C2...");
        });               
        // 對於類方法, 須要使用 std::bind.
        std::thread t(std::bind(&ThreadExample::MyThread, this, "msg"));
        ThreadGuard tg(t);

若是 std::thread 對象銷燬以前尚未調用 join 或 detach, 程序就會終止( std::thread 的析構函數會調用 std::terminate() ). 所以, 即使是有異常存在, 也須要確保線程可以正確的加入(joined)或分離(detached).
調用 join 或 detach 以前須要調用 joinable() 判斷一下線程是否運行. 若是 joinable() 返回 false, 則不須要.
join()是簡單粗暴的等待線程完成, 此時建立 std::thread 對象的線程(如下稱主線程)將被阻塞. 若是在線程啓動後到主線程在調用 join() 前的代碼中發生了異常, 此時將會致使主線程永遠沒有機會執行.
針對此問題, 須要使用 RAII 機制來解決, 如建立一個 ThreadGuard 對象, 在析構函數中保證老是能夠調用到 join.oop

        #ifndef _THREAD_GUARD_
        #define _THREAD_GUARD_

        #include <thread>

        class ThreadGuard
        {
        public:
            ThreadGuard(std::thread& t_) : t(t_){}

            ~ThreadGuard()
            {
                if (t.joinable())
                {
                    t.join();
                }
            }

            ThreadGuard(const ThreadGuard &) = delete;
            ThreadGuard& operator=(const ThreadGuard &) = delete;

        private:
            std::thread& t;
        };

        #endif // _THREAD_GUARD_

若是是分離線程, 必須保證可訪問數據的有效性, 不然會產生未定義的行爲, 如同單線程中一個對象被銷燬後再訪問同樣.
處理這種狀況的常規方法: 使線程函數的功能齊全, 將數據複製到線程中. 若是使用一個可調用的對象做爲線程函數,這個對象就會複製到線程中,然後原始對象就能夠銷燬. 下面是錯誤的使用方法示例:this

        class Func
        {
            int& i;
        public:
            Func(int& i_) : i(i_) {}
            void operator() ()
            {
                for (unsigned j = 0; j < 10; ++j)
                {
                    // 潛在訪問隱患:懸空引用 i 
                    std::cout << i << " ";
                }
                std::cout << std::endl;
            }
        };
        { // 某個做用域內
            int* p = new int(100);
            Func f(*p);
            std::thread t(f);
            t.detach(); // 不等待線程結束
            delete p;
        } // 新線程可能還在運行

 

線程函數

  線程函數能夠有不一樣的參數, 向線程傳遞參數,只要在構造 std::thread 對象時,按照線程函數參數列表一一對應傳入便可。線程函數有幾點須要注意的地方:spa

(1) 默認的參數會被拷貝到獨立的線程中,即便是引用的形式, 若是須要須要傳遞引用, 須要使用 std::ref 顯示說明(而且線程函數參數也須要聲明爲引用).線程

        void ThreadParamRef(std::string& str)
        {
            str += " --> add";
        }

        void ThreadParam(std::string str)
        {
            str += " --> add";
        }    
    
        std::string str("Hello C++ Thread...");
        //std::thread t(ThreadParamRef, str);
        std::thread t(ThreadParamRef, std::ref(str)); // 只有這種形式才能在線程執行完畢後輸出 Hello C++ Thread... --> add 
        //std::thread t(ThreadParam, std::ref(str));
        t.join();
        std::cout << str << std::endl;

(2)  線程參數傳遞時須要注意不能傳入局部變量, 考慮下面的代碼,buffer②是一個指針變量,指向本地變量,而後本地變量經過buffer傳遞到新線程中②。
  函數有很大的可能,會在字面值轉化成 std::string 對象以前崩潰,從而致使線程的一些未定義行爲。
  解決方案就是在傳遞到 std::thread 構造函數以前就將字面值轉化爲 std::string 對象。指針

        void f(int i,std::string const& s);
        void oops(int some_param)
        {
            char buffer[1024]; // 1
            sprintf(buffer, "%i",some_param);
            std::thread t(f,3,buffer); // 2
            t.detach();
        }          
        // 正確的方法
        void f(int i,std::string const& s);
        void not_oops(int some_param)
        {
            char buffer[1024];
            sprintf(buffer,"%i",some_param);
            std::thread t(f,3,std::string(buffer)); // 使用std::string,避免懸垂指針
            t.detach();
        }

  (3) 線程函數參數傳遞時, 能夠移動, 但不能拷貝. "移動"是指: 原始對象中的數據轉移給另外一對象,而轉移的這些數據在原始對象中再也不保存.code

        void ThreadParamUniquePtr(std::unique_ptr<int> up)
        {
            std::cout << (up.get() ? *up : -1) << std::endl;
        }
        std::thread t(ThreadParamUniquePtr, std::move(up));
        //std::thread t(ThreadParamUniquePtr, up);  // 不能編譯
        //std::thread t(ThreadParamUniquePtr, std::ref(up)); // 要求線程函數參數也爲引用才能編譯
        t.join();
        std::cout << (up.get() ? *up : -1) << std::endl; // 將輸出-1

 

線程全部權轉移

  線程是資源獨佔型, 但能夠將全部權轉移給別的對象. 若是一個 std::thread 對象與一個運行的線程關聯, 此時接受一個新的線程全部權時, 其之前關聯的線程將直接調用 std::terminate() 終止程序繼續運行.對象

std::thread t1(f);
std::thread t2(f);
// t1 = std::thread(f); // t1 全部權尚未轉移, 不能經過賦一個新值來放棄線程
// t1 = std::move(t2); // t1 全部權尚未轉移, 不能經過賦一個新值來放棄線程
t1.detach(); 或 t1.join();
t1 = std::move(t2);
std::thread t3 = std::move(t1);
t1 = std::move(t2);blog

線程對象也能夠在函數中進行轉移.

std::thread f1()
{
  return std::thread(f);
}
std::thread f2()
{
  std::thread t(f);
  return t;
}
void f3(std::thread t);
void f4()
{
  f3(std::thread(f));
  std::thread t(f);
  f3(std::move(t));
}

因爲 std::thread 是可轉移的, 若是容器對移動操做支持, 則能夠將 std::thread 對象放入其中.

        class Func
        {
            int i;
        public:
            Func(int i_) : i(i_) {}
            void operator() ()
            {
                for (unsigned j = 0; j < 10; ++j)
                {
                    std::cout << i << " ";
                }
                std::cout << std::endl;
            }
        };
        std::vector<std::thread> threads;
        for (int i = 1; i < 10; i++)
        {
            Func f(i);
            //std::thread t(f);
            //v.push_back(t);     // 不能採用這種方式
            //v.push_back(std::move(t));  // 須要使用移動操做才能夠
            threads.push_back(std::thread(f));
        }
        std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join)); // 對每一個線程調用join()

 

經常使用函數

  std::thread::hardware_concurrency() 返回 CPU 核心線程數. 若是沒法查詢系統信息時, 返回0. (static 函數)
  get_id() 返回 std::thread 對象關聯的線程的 id. 若是全部權已轉移, 或線程函數已返回, 返回0.
  std::this_thread::get_id() 取得當前線程的 id. (static 函數)

 

 

一個更好的ThreadGuard

#ifndef _THREAD_GUARD_
#define _THREAD_GUARD_

template <class _Thread>
class ThreadGuard
{
public:
    explicit ThreadGuard(_Thread& t_) : t(t_) {}

    ~ThreadGuard()
    {
        if (t.joinable())
        {
            t.join();
        }
    }

    ThreadGuard(const ThreadGuard &) = delete;
    ThreadGuard& operator=(const ThreadGuard &) = delete;

private:
    _Thread& t;
};

#endif // _THREAD_GUARD_
相關文章
相關標籤/搜索