C++11マルチスレッド基本使用

18215 ワード

C++11はスレッド及びスレッドに関する疲れを増加し、同時プログラミングを便利にサポートし、作成したマルチスレッドプログラムの移植性を大幅に向上させた.
スレッドの作成
std::threadでスレッドを作成するのは簡単です.スレッド関数または関数オブジェクトを指定するだけで、スレッドのパラメータを同時に指定できます.
#include
#include
#include
using namespace std;

//    
void func(int a, int b, int c)
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    cout << a << " " << b << " " << c << endl;
}

int main()
{
    //      t1,       func
    std::thread t1(func, 1, 2, 3);
    //  t1   ID
    std::cout << "ID:" << t1.get_id() << std::endl;
    //  t1        
    t1.join();
    std::thread t2(func, 2, 3, 4);
    //    t2     ,      main     ,             
    t2.detach();
    cout << "after t2 ,main is runing" << endl;
    // lambda             
    std::thread t4([](int a, int b, int c)
        {
            //    5 
            std::this_thread::sleep_for(std::chrono::seconds(5)); 
            cout << a << " " << b << " " << c << endl;
        }, 4,5,6);
    t4.join();

    //  CPU   
    cout << "CPU: " << thread::hardware_concurrency() << endl;
    //                ,               ,                          .
    //std::thread t3(func, 3, 4, 5);

    return 0;
}
  • スレッド関数はスレッドオブジェクトtで実行する、join関数はスレッドをブロックし、スレッド関数の実行が終了するまで、スレッド関数に戻り値がある場合、戻り値は無視される.
  • detachはスレッドをスレッドオブジェクトから分離する、スレッドをバックグラウンドスレッドとして実行させ、現在のスレッドもブロックすることはない.しかしdetach以降はスレッドと連絡が取れなくなる.スレッド実行関数が一時変数を使用する場合、スレッドがdetachを呼び出してバックグラウンドで実行する、一時変数が破棄された可能性がある場合、スレッドは破棄された変数にアクセスする.ジョンは保証できる
  • このようにスレッドを作成するのは便利であるが、std::threadが役割ドメインを生成すると解析され、このときスレッド関数が実行されていないとエラーが発生する.
  • スレッドはコピーできないが移動できる.しかし、スレッドが移動すると、スレッドオブジェクトはスレッドを表しません.
        std::thread t(func);
        //   ,    t        
        std::thread t1(std::move(t));
        // t.join();
        t1.join();
  • はんぱつりょう
    反発量は同期原語であり、マルチスレッドが同時にアクセスする共有データを保護するためのスレッド同期手段である.
  • std::mutex:排他的な反発量は再帰的に使用できない.
  • std::timed_mutex:タイムアウトを伴う排他的反発量は,再帰的に使用できない.
  • std::recursive_mutex:再帰反発量、タイムアウト機能を持たない.
  • std::recursive_timed_mutex:タイムアウト付き再帰反発量.

  • これらの反発量の基本インタフェースは、反発量の所有権が得るまでlock()によってスレッドをブロックするものに非常に近い.スレッドまたは反発量がタスクを完了すると、unlock()を使用して反発量の占有を解除する必要があり、lockとunlockがペアで現れる必要がある.try_lock()は反発量をロックしようとしたが、trueに戻ることに成功し、falseに戻ることに失敗した.彼はブロックしていない.
    #include<iostream>
    #include<thread>
    #include<mutex>
    using namespace std;
    
    std::mutex g_lock;
    
    void func()
    {
        //  
        g_lock.lock();
        cout << "in id: " << this_thread::get_id() << endl;
        this_thread::sleep_for(chrono::seconds(1));
        cout << "out id: " << this_thread::get_id() << endl;
        //  
        g_lock.unlock();
    }
    
    void f()
    {
        //lock_guard            ,                   .
        lock_guard<std::mutex> lock(g_lock);
        cout << "in id: " << this_thread::get_id() << endl;
        this_thread::sleep_for(chrono::seconds(1));
        cout << "out id: " << this_thread::get_id() << endl;
    }
    
    int main()
    {
        std::thread t1(func);
        std::thread t2(func);
        std::thread t3(func);
    
        t1.join();
        t2.join();
        t3.join();
    
        std::thread t4(f);
        std::thread t5(f);
        std::thread t6(f);
    
        t4.join();
        t5.join();
        t6.join();
    }
  • lock_guardはRAIIの技術を用いて、この技術はクラスの構造関数の中で資源を分配して、構造関数の中で資源を釈放して、資源が作用ドメインを出した後で釈放することを保証します.
  • std::recursive_mutex再帰ロックは、同じスレッドが複数回反発量を得ることを可能にする.ただし、再帰ロックは使用しないでください.
  • 再帰ロックを必要とするマルチスレッド反発処理は、往々にしてそれ自体が簡略化され、再帰相互が複雑な論理の発生を容易に放縦することができ、それによっていくつかのマルチスレッド同期による難解な問題を引き起こす.
  • の再帰ロックは非再帰ロックよりも効率が低い.
  • 再帰ロックは、同一スレッドで同一反発量を複数回得ることを可能とするが、反復可能な最大回数は具体的には説明するておらず、一定回数を超えると異常が放出される.

  • タイムアウト付き反発量は、ロックを取得する際にタイムアウト待ち機能を追加する、ロックを取得するのにどのくらいかかるか分からない場合があるため、反発量を取得するまで待たないために、待機タイムアウト時間を設定し、タイムアウト後に他のことをすることもできる.
  • #include<iostream>
    #include<thread>
    #include<mutex>
    #include<chrono>
    using namespace std;
    
    std::timed_mutex mutex1;
    
    void work()
    {
        //      
        std::chrono::milliseconds timeout(100);
    
        while (true) {
            //     ,     100milliseconds   false
            if (mutex1.try_lock_for(timeout)) {
                cout << this_thread::get_id() << ": do work with the mutex" << endl;
                std::chrono::milliseconds sleepDuration(250);
                this_thread::sleep_for(sleepDuration);
            } else {
                cout << this_thread::get_id() << ": do work without mutex" << endl;
    
                chrono::milliseconds sleepDuration(100);
                std::this_thread::sleep_for(sleepDuration);
            }
        }
    }
    
    int main()
    {
        std::thread t1(work);
        std::thread t2(work);
    
        t1.join();
        t2.join();
    
        return 0;
    }

    じょうけんへんすう
  • 条件変数は、1つまたは複数のスレッドをブロックする、別のスレッドからの通知またはタイムアウトが受信されるまで、現在のブロックのプロセスを呼び覚ます、条件変数は反発量と組み合わせて使用する必要がある.
  • C++11は2種類の条件変数を提供する
  • std::condition_variable,配合std::unique_lockはwait操作
  • を行う
  • std::condition_variable_anyは、lock、unlockを任意に持つmutexと組み合わせて使用し、柔軟だが効率はやや低い.

  • 条件変数の使用手順は次のとおりです.
  • 条件変数を有するスレッド取得反発ロック
  • は、ある条件をループ検査し、条件が満たされない場合は条件が満たされるまでブロックし、条件が満たされる場合は下へ実行する.
  • スレッドが条件を満たして実行された後にnotifyを呼び出すoneまたはnotify_allは1つまたはすべての待機スレッドを起動する.


  • eg:
    //       
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    
    template <typename T>
    class SyncQueue
    {
    private:
        //     
        std::list m_queue;
        //   
        std::mutex m_mutex;
        //        
        std::condition_variable_any m_notFull;
        //        
        std::condition_variable_any m_notEmpty;
        //       
        int m_maxsize;
    
        //      ,           ,                ,       
        bool IsFull()
        {
            return m_queue.size() == m_maxsize;
        }
    
        //      
        bool IsEmpty()
        {
            return m_queue.empty();
        }
    public:
        SyncQueue(int max):m_maxsize(max) {  }
        //        
        void Put(const T& x)
        {
            //unique_lock lock_guard  ,              ,          
            std::unique_guard<std::mutex> locker(m_mutex);
    
            //       ,          
            while (IsFull())
            {
                std::cout << "data Full" << std::endl;
                //   ,          ,    m_mutex ,     notify_one  notify_all      m_mutex 
                m_notFull.wait(m_mutex);
            }
            //        
            m_queue.push_back(x);
            //              
            m_notEmpty.notify_one();
        }
    
        //        
        void Take(T& x)
        {
            std:unique_guard<std::mutex> locker(m_mutex);
    
            //        ,         Empty,    while     .
            //m_notEmpty.wait(locker, [this] {return !m_queue.empty();});
    
            //       ,          
            while(IsEmpty())
            {
                std::cout << "data Empty" << std::endl;
                m_notEmpty.wait(m_mutex);
            }
            //    
            x = m_queue.front();
            //        
            m_queue.pop_front();
            m_notFull.notify_one();
        }
    
        bool Empty()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.empty();
        }
    
        bool Full()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.size() == m_maxsize;
        }
    
        size_t Size()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.size();
        }
    };
    

    げんしへんすう
  • C++11は、任意のタイプをテンプレートパラメータとして使用することができる原子タイプstd::atomicを提供する、C++11には整性の原子変数が内蔵されており、原子変数を使用すると反発量を使用することなく変化量を保護することができる.
  • #include
    
    struct AtomicCounter {
        std::atomic<int> value;
    
        void increment()
        {
            ++ value;
        }
    
        void decrement()
        {
            -- value;
        }
    
        int get()
        {
            return value;
        }
    };

    call_once/once_flag
  • マルチスレッド環境で1つの関数が1回のみ呼び出されることを保証するために、例えば、あるオブジェクトを初期化する必要があり、このオブジェクト知能が1回初期化されるとstd::call_を使用することができるonceは、関数がマルチスレッド環境で一度だけ呼び出されることを保証する.
  • #include
    #include
    #include
    
    std:once_flag flag;
    
    void do_once()
    {
        std::call_once(flag, []() {std::cout << "called" << std::endl;});
    }
    
    int main()
    {
        std::thread t1(do_once);
        std::thread t2(do_once);
        std::thread t3(do_once);
    
        t1.join();
        t2.join();
        t3.join();
    
        return 0;
    }