程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++11 多線程 -- 基本使用

c++11 多線程 -- 基本使用

編輯:C++入門知識

c++11 多線程 -- 基本使用


c++11 多線程 – 基本使用

前言:這篇文章僅針對沒有使用過c++11線程庫的童鞋來快速入門,也是自己的一個簡單記錄,內容比較基礎。

-1.線程的基本使用
-2.互斥量
-3.條件變量
-4.原子變量


1.線程的基本使用

代碼:

#include 
#include 
#include 
#include 
#include 

int k = 0;

void fun(void)
{
    //線程休眠,chrono是c++11的時間相關庫。
          std::this_thread::sleep_for(std::chrono::seconds(3));
    for(int i = 0; i < 10; ++i)
    {
        std::cout << hello world << std::endl;
        k++;
    }
}

int main(int argc, char *argv[])
{
    //創建線程對象
    std::thread t1(fun);
    //輸出線程id和cpu核數
    std::cout << ID: << t1.get_id() << std::endl;
    std::cout << CPU: << std::thread::hardware_concurrency() << std::endl;
    //主函數阻塞等待線程結束
    t1.join();
    //主函數和線程函數分離執行,線程變為後台線程
    //t1.detach();

    std::cout << k << std::endl;

    return EXIT_SUCCESS;
}

注意:
1.linux下用gcc或clang必須加-pthread連接到線程庫,否則會出錯。
2.主線程函數不能提前結束於新創建的線程函數,因為在c++11中,線程也是對象,主函數結束線程對象即銷毀。
3.t.join()是主函數阻塞等待線程結束才能結束,主函數會繼續執行,並阻塞在return處
t.detach()主函數和線程函數分離,各自執行各自的,線程變為後台線程。
4.可通過bind和lambda創建線程
可以將線程保存在容器中,以保證線程對象的聲明周期。
但是注意線程沒有拷貝構造函數,有移動構造函數。
這裡寫圖片描述
圖上可以看出拷貝構造函數為delete。


2.互斥量

分為4種
std::mutex 獨占的互斥量,不能遞歸使用
std::timed_mutex 帶超時的獨占的互斥量,不能遞歸使用
std::recursive_mutex 遞歸互斥量,不帶超時功能
std::recursive_timed_mutex 帶超時的遞歸互斥量

代碼:

#include 
#include 
#include 
#include 
#include 
#include 

std::mutex g_lock;
int i = 0;

void func(void)
{
    //使用RAII手法,在離開作用域時自動釋放
    std::lock_guardlocker(g_lock);
    //正常的互斥鎖上鎖
    //g_lock.lock();
    i++;
    std::cout << i << std::endl;
    //互斥鎖解鎖
    //g_lock.unlock();
}

int main(int argc, char *argv[])
{
    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);
    t1.join();
    t2.join();
    t3.join();

    return EXIT_SUCCESS;
}

注意:
1.多次獲取互斥量可能會發生死鎖,所以我們調用std::recursive_mutex遞歸鎖,允許同一線程多次獲得該鎖,一般不要使用遞歸鎖,原因:<1.用到遞歸鎖會使得程序的邏輯變復雜,使用到遞歸鎖的程序一般可以簡化。<2.遞歸鎖比非遞歸鎖效率低。<3.遞歸鎖的可重入次數是有限的,超過也會報錯。
2.可以使用帶超時時間的互斥鎖,避免阻塞在等待互斥鎖上。
3.unique_lock: 是一個通用的互斥量封裝類。與lock_guard不同,它還支持延遲加鎖、時間鎖、遞歸鎖、鎖所有權的轉移並且還支持使用條件變量。這也是一個不可復制的類,但它是可以移動的類。


3.條件變量

阻塞一個或多個線程,直到收到另外一個線程發來的通知或者超時,才會喚醒當前阻塞的進程
條件變量需要和互斥量配合使用
c++11提供了兩種條件變量
1.std::condition_variable,配合std::unique_lock進行wait操作
2.std::condition_variable_any,和任意帶有lock,unlock的mutex進行搭配使用,比較靈活但效率略低。
條件變量的wait還有一個重載的方法,可以設置一個條件,條件變量會先檢查判斷式是否滿足條件。

原理:
當 std::condition_variable 對象的某個 wait 函數被調用的時候,它使用 std::unique_lock(通過 std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調用了 notification 函數來喚醒當前線程。

代碼:用c++11多線程實現同步隊列

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

template
class SynQueue
{
    public:
        //構造函數
        SynQueue(int MaxSize):
            m_maxsize(MaxSize) { }

        //將T類型對象放入隊列
        void Put(const T&x)
        {
            std::lock_guardlocker(m_mutex);
            while(isFull())
            {
                //如果滿了,等待
                m_notFull.wait(m_mutex);
            }
            m_queue.push_back(x);
            //通過條件變量喚醒一個線程,也可以所有線程
            m_notEmpty.notify_one();
        }

        //將T類型對象從隊列取出
        void Take(T&x)
        {
            std::lock_guard locker(m_mutex);
            while(isEmpty())
            {
                std::cout << no resource... please wait << std::endl;
                m_notEmpty.wait(m_mutex);
            }
            x = m_queue.front();
            m_queue.pop_front();
            m_notFull.notify_one();
        }

        //判斷隊列是否為空
        bool Empty()
        {
            std::lock_guard locker(m_mutex);
            return m_queue.empty();
        }

        //判斷隊列是否為滿
        bool Full()
        {
            std::lock_guard locker(m_mutex);
            return m_queue.size() == m_maxsize;
        }

        //返回隊列大小
        size_t Size()
        {
            std::lock_guard locker(m_mutex);
            return m_queue.size();
        }

    private:
        //判斷空或滿,內部使用不需要加鎖
        bool isFull() const
        {
            return m_queue.size() == m_maxsize;
        }
        bool isEmpty() const
        {
            return m_queue.empty();
        }

    private:
        //隊列
        std::listm_queue;
        //互斥鎖
        std::mutex m_mutex;
        //不為空時的條件變量
        std::condition_variable_any m_notEmpty;
        //不為滿時的條件變量
        std::condition_variable_any m_notFull;
        //隊列最大長度
        int m_maxsize;
};

void func(SynQueue *sq)
{
    int ret;
    sq->Take(ret);
    std::cout << ret << std::endl;
}

int main(int argc, char *argv[])
{
    //創建線程隊列,長度最大為20
    SynQueuesyn(20);
    //放置數據對象
    for(int i = 0; i < 10; i++)
    {
        syn.Put(i);
    }
    std::cout << syn.Size() << std::endl;

    //線程不能拷貝,用容器和智能指針來管理線程生存
    std::vector> tvec;
    //多循環一次,資源不足,阻塞最後一個線程,在後面添加一個資源,看該線程是否會被喚醒執行。
    for(int i = 0; i < 11; i++)
    {
        //創建線程並且將管理線程的智能指針保存到容器中
        tvec.push_back(std::make_shared(func, &syn));
        //變為後台線程
        tvec[i]->detach();
    }
    sleep(10);
    //添加一個資源
    syn.Put(11);
    sleep(10);

    return EXIT_SUCCESS;
}

運行結果:
這裡寫圖片描述


4.原子變量

原子變量,為原子操作,不需要加鎖
std::atomic
詳情可參考,這裡僅簡單舉例用法
cppreference atomic

代碼:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

//創建int類型的原子變量
std::atomicatc(0);

void func()
{
    std::cout << atc << std::endl;
    原子變量自增
    atc++;
}

int main(int argc, char *argv[])
{
    std::vectortvec;
    for(int i = 0; i < 10; i++)
    {
        std::thread t(func);
        //線程對象移動語義
        tvec.push_back(std::move(t));
        tvec[i].join();
    }

    return EXIT_SUCCESS;
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved