ミューテックスと条件変数の性能比較C ++並列プログラミング

入門

この論文では、最も単純な生産者-消費者モデルは、プログラムを実行することによって、の使用を比較するために、プロセスのCPU使用率を観察ミューテックスミューテックス+状態変数性能比較を。

本実施例の生産者と消費者モデル、1つの生産、5消費者目。
5つの消費者のスレッドがキューからデータをフェッチし、データにキューにプロデューサーのスレッドは、あなたがキューにデータがあるかどうかを確認するために、データを取る前に決定する必要がある、キューは、グローバルキューで、データはスレッド間で共有されるので、ミューテックスを使用する必要がありますロック保護。生産者は、キューデータに入れたとき、すなわち、消費者はその逆の残りの部分に取得し、することはできません。


ミューテックスコードを達成

#include <iostream> // std::cout
#include <deque>    // std::deque
#include <thread>   // std::thread
#include <chrono>   // std::chrono
#include <mutex>    // std::mutex


// 全局队列
std::deque<int> g_deque;

// 全局锁
std::mutex g_mutex;

// 生产者运行标记
bool producer_is_running = true;

// 生产者线程函数
void Producer()
{
    // 库存个数
    int count = 8;
    
    do
    {
        // 智能锁,初始化后即加锁,保护的范围是代码花括号内,花括号退出即会自动解锁
        // 可以手动解锁,从而控制互斥锁的细粒度
        std::unique_lock<std::mutex> locker( g_mutex );
        // 入队一个数据
        g_deque.push_front( count );
        // 提前解锁,缩小互斥锁的细粒度,只针对共享的队列数据进行同步保护
        locker.unlock(); 

        std::cout << "生产者    :我现在库存有 :" << count << std::endl;
            
        // 放慢生产者生产速度,睡1秒
        std::this_thread::sleep_for( std::chrono::seconds( 1 ) );

        // 库存自减少
        count--;
    } while( count > 0 );
    
    // 标记生产者打样了
    producer_is_running = false;

    std::cout << "生产者    : 我的库存没有了,我要打样了!"  << std::endl;
}

// 消费者线程函数
void Consumer(int id)
{
    int data = 0;

    do
    {
        std::unique_lock<std::mutex> locker( g_mutex );
        if( !g_deque.empty() )
        {
            data = g_deque.back();
            g_deque.pop_back();
            locker.unlock();

            std::cout << "消费者[" << id << "] : 我抢到货的编号是 :" << data << std::endl;
        }
        else
        {
            locker.unlock();
        }
    } while( producer_is_running );
    
    std::cout << "消费者[" << id << "] :卖家没有货打样了,真可惜,下次再来抢!"  << std::endl;
}

int main(void)
{
    std::cout << "1 producer start ..." << std::endl;
    std::thread producer( Producer );

    std::cout << "5 consumer start ..." << std::endl;
    std::thread consumer[ 5 ];
    for(int i = 0; i < 5; i++)
    {
        consumer[i] = std::thread(Consumer, i + 1);
    }

    producer.join();

    for(int i = 0; i < 5; i++)
    {
        consumer[i].join();
    }

    std::cout << "All threads joined." << std::endl;

    return 0;
}

業績を達成するためにミューテックス:

結果出力

[root@lincoding condition]# g++ -std=c++0x -pthread -D_GLIBCXX_USE_NANOSLEEP main.cpp -o  main
[root@lincoding condition]# ./main
1 producer start ...
5 consumer start ...
生产者    :我现在库存有 :8
消费者[1] : 我抢到货的编号是 :8
消费者[1] : 我抢到货的编号是 :7
生产者    :我现在库存有 :7
生产者    :我现在库存有 :6
消费者[3] : 我抢到货的编号是 :6
生产者    :我现在库存有 :5
消费者[1] : 我抢到货的编号是 :5
生产者    :我现在库存有 :4
消费者[2] : 我抢到货的编号是 :4
生产者    :我现在库存有 :3
消费者[5] : 我抢到货的编号是 :3
生产者    :我现在库存有 :2
消费者[2] : 我抢到货的编号是 :2
生产者    :我现在库存有 :1
消费者[1] : 我抢到货的编号是 :1
生产者    : 我的库存没有了,我要打样了!消费者[
5] :卖家没有货打样了,真可惜,下次再来抢!
消费者[2] :卖家没有货打样了,真可惜,下次再来抢!
消费者[3] :卖家没有货打样了,真可惜,下次再来抢!
消费者[4] :卖家没有货打样了,真可惜,下次再来抢!
消费者[1] :卖家没有货打样了,真可惜,下次再来抢!
All threads joined.

ミューテックスが実際にこのタスクを達成することができます見ることができますが、パフォーマンスの問題があります。

  • Producer生産者スレッド、プロデューサーのデータ処理は、休みますされ1秒、その製造工程が非常に遅いです。

  • Consumer消費者のスレッドがあるwhileサイクルが唯一のプロデューサーが実行されないかを決定するために、そして終了するwhileループを、体内のすべての時間は、ロックされたループは、最初のキューが空でないかを決定し、その後、データキューから削除し、最後にロックを解除します。だから、休憩プロデューサー1秒時間、消費者のスレッドが実際にCPU使用率で、その結果、無駄な努力の多くを行うには非常に高いです!

動作環境は、4コアのCPUであります

[root@lincoding ~]# grep 'model name' /proc/cpuinfo | wc -l
4

トップコマンドは、CPU使用率を確認するために、目に見える使用して純粋なミューテックスCPUのオーバーヘッドが大きい、mainプロセスのCPU使用率に達し357.5%CPU、システムオーバヘッドは、CPUであり54.5%sy、ユーザのCPUオーバーヘッドが18.2%us

[root@lincoding ~]# top
top - 19:13:41 up 36 min,  3 users,  load average: 0.06, 0.05, 0.01
Tasks: 179 total,   1 running, 178 sleeping,   0 stopped,   0 zombie
Cpu(s): 18.2%us, 54.5%sy,  0.0%ni, 27.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1004412k total,   313492k used,   690920k free,    41424k buffers
Swap:  2031608k total,        0k used,  2031608k free,    79968k cached

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                       
 35346 root      20   0  137m 3288 1024 S 357.5  0.3   0:05.92 main                                                                                                                          
     1 root      20   0 19232 1492 1224 S  0.0  0.1   0:02.16 init                                                                                                                           
     2 root      20   0     0    0    0 S  0.0  0.0   0:00.01 kthreadd                                                                                                                       
     3 root      RT   0     0    0    0 S  0.0  0.0   0:00.68 migration/0  

解決策の1つは、消費者が休憩時間に、データを取得していない時にわずかな遅延を追加し、消費者を与えることです500毫秒ので、CPUをもたらすために、ミューテックスのオーバーヘッドを削減します、。

// 消费者线程函数
void Consumer(int id)
{
    int data = 0;

    do
    {
        std::unique_lock<std::mutex> locker( g_mutex );
        if( !g_deque.empty() )
        {
            data = g_deque.back();
            g_deque.pop_back();
            locker.unlock();

            std::cout << "消费者[" << id << "] : 我抢到货的编号是 :" << data << std::endl;
        }
        else
        {
            locker.unlock();
            // 当消费者没取到数据时,就休息一下500毫秒
            std::this_thread::sleep_for( std::chrono::milliseconds( 500 ) );
        }
    } while( producer_is_running );
    
    std::cout << "消费者[" << id << "] :卖家没有货打样了,真可惜,下次再来抢!"  << std::endl;
}

実行中の結果が示すより、CPU使用率が大幅に削減します

[root@lincoding ~]# ps aux | grep -v grep  |grep main
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      61296  0.0  0.1 141068  1244 pts/1    Sl+  19:40   0:00 ./main

実装ミューテックス、条件変数コード+

そこで問題は、どのくらいのために、消費者の遅延(残り)をどのように決定するか、でしょうか?

  • 生産者は非常に速く生成する場合、消費者は遅れている500毫秒非常に良いではありません、
  • 生産者はもっとゆっくり、生産する場合、消費遅れて500毫秒、CPUを占有して、無駄な努力があるでしょう

これは、条件変数の導入が必要ですstd::condition_variable、消費者のモデルの生産に使用されるが、を通じて、完全な生産データの後にプロデューサーであるnotify_one()目覚めるwait()消費者スレッド、消費者がキューからデータを削除することができます。

#include <iostream> // std::cout
#include <deque>    // std::deque
#include <thread>   // std::thread
#include <chrono>   // std::chrono
#include <mutex>    // std::mutex

#include <condition_variable> // std::condition_variable


// 全局队列
std::deque<int> g_deque;

// 全局锁
std::mutex g_mutex;

// 全局条件变量
std::condition_variable g_cond;

// 生产者运行标记
bool producer_is_running = true;

// 生产者线程函数
void Producer()
{
    // 库存个数
    int count = 8;
    
    do
    {
        // 智能锁,初始化后即加锁,保护的范围是代码花括号内,花括号退出即会自动解锁
        // 可以手动解锁,从而控制互斥锁的细粒度
        std::unique_lock<std::mutex> locker( g_mutex );
        // 入队一个数据
        g_deque.push_front( count );
        // 提前解锁,缩小互斥锁的细粒度,只针对共享的队列数据进行同步保护
        locker.unlock(); 

        std::cout << "生产者    :我现在库存有 :" << count << std::endl;
        
        // 唤醒一个线程
        g_cond.notify_one();
        
        // 睡1秒
        std::this_thread::sleep_for( std::chrono::seconds( 1 ) );

        // 库存自减少
        count--;
    } while( count > 0 );
    
    // 标记生产者打样了
    producer_is_running = false;
    
    // 唤醒所有消费线程
    g_cond.notify_all();
    
    std::cout << "生产者    : 我的库存没有了,我要打样了!"  << std::endl;
}

// 消费者线程函数
void Consumer(int id)
{
    // 购买的货品编号
    int data = 0;

    do
    {
        // 智能锁,初始化后即加锁,保护的范围是代码花括号内,花括号退出即会自动解锁
        // 可以手动解锁,从而控制互斥锁的细粒度
        std::unique_lock<std::mutex> locker( g_mutex );
        
        // wait()函数会先调用互斥锁的unlock()函数,然后再将自己睡眠,在被唤醒后,又会继续持有锁,保护后面的队列操作
        // 必须使用unique_lock,不能使用lock_guard,因为lock_guard没有lock和unlock接口,而unique_lock则都提供了
        g_cond.wait(locker); 
        
        // 队列不为空
        if( !g_deque.empty() )
        {
            // 取出队列里最后一个数据
            data = g_deque.back();
            
            // 删除队列里最后一个数据
            g_deque.pop_back();
            
            // 提前解锁,缩小互斥锁的细粒度,只针对共享的队列数据进行同步保护
            locker.unlock(); 

            std::cout << "消费者[" << id << "] : 我抢到货的编号是 :" << data << std::endl;
        }
        // 队列为空
        else
        {
            locker.unlock();
        }
    
    } while( producer_is_running );
    
    std::cout << "消费者[" << id << "] :卖家没有货打样了,真可惜,下次再来抢!"  << std::endl;
}

int main(void)
{
    std::cout << "1 producer start ..." << std::endl;
    std::thread producer( Producer );

    std::cout << "5 consumer start ..." << std::endl;
    std::thread consumer[ 5 ];
    for(int i = 0; i < 5; i++)
    {
        consumer[i] = std::thread(Consumer, i + 1);
    }

    producer.join();

    for(int i = 0; i < 5; i++)
    {
        consumer[i].join();
    }

    std::cout << "All threads joined." << std::endl;

    return 0;
}

+ミューテックス、条件変数の営業成績

[root@lincoding condition]# g++ -std=c++0x -pthread -D_GLIBCXX_USE_NANOSLEEP main.cpp -o  main
[root@lincoding condition]# 
[root@lincoding condition]# ./main 
1 producer start ...
5 consumer start ...
生产者    :我现在库存有 :8
消费者[4] : 我抢到货的编号是 :8
生产者    :我现在库存有 :7
消费者[2] : 我抢到货的编号是 :7
生产者    :我现在库存有 :6
消费者[3] : 我抢到货的编号是 :6
生产者    :我现在库存有 :5
消费者[5] : 我抢到货的编号是 :5
生产者    :我现在库存有 :4
消费者[1] : 我抢到货的编号是 :4
生产者    :我现在库存有 :3
消费者[4] : 我抢到货的编号是 :3
生产者    :我现在库存有 :2
消费者[2] : 我抢到货的编号是 :2
生产者    :我现在库存有 :1
消费者[3] : 我抢到货的编号是 :1
生产者    : 我的库存没有了,我要打样了!
消费者[5] :卖家没有货打样了,真可惜,下次再来抢!
消费者[1] :卖家没有货打样了,真可惜,下次再来抢!
消费者[4] :卖家没有货打样了,真可惜,下次再来抢!
消费者[2] :卖家没有货打样了,真可惜,下次再来抢!
消费者[3] :卖家没有货打样了,真可惜,下次再来抢!
All threads joined.

CPUのオーバーヘッドは非常に小さいです

[root@lincoding ~]# ps aux | grep -v grep  |grep main
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root      73838  0.0  0.1 141068  1256 pts/1    Sl+  19:54   0:00 ./main

概要

生産速度の不確実性の生産者にだけ共有データを保護するためのミューテックスを使用しないで、高速または低速のシーンで、このCPUのパフォーマンスのオーバーヘッドが非常に大きい道ミューテックス+条件変数を使用することができますがあります、とき生産誰が無駄な努力のパフォーマンスのオーバーヘッドを避けるため、彼らは消費者のスレッドの消費量を覚ます、スレッドデータを生成します。

おすすめ

転載: www.cnblogs.com/xiaolincoding/p/11441568.html