簡単にc++11スレッドの反発量を話します。


なぜ互角の量が必要ですか?
マルチタスクオペレーティングシステムでは、同時に実行される複数のタスクは、同じリソースを使用する必要があります。このプロセスはちょっと似ています。会社の部門では、プリンターを使ってものをプリントしています。他の人もちょうど今プリンターを使ってものをプリントしています。何もしないと、印刷されたものは間違いないです。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <chrono>
#include <thread>

//    
void printer(const char *str)
{
    while(*str != '\0')
    {
        std::cout << *str;
        str++;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << std::endl;
}

//    
void func1()
{
    const char *str = "hello";
    printer(str);
}

//    
void func2()
{
    const char *str = "world";
    printer(str);
}


void mytest()
{
    std::thread t1(func1);
    std::thread t2(func2);

    t1.join();
    t2.join();

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
独占相互反発量std:mutex
相互反発量の基本インターフェースは類似しており、一般的にはロック()法によってスレッドがブロックされ、相互反発量の所有権が得られるまで使用される。スレッドが相互反発量を得てタスクを完了すると、アンロック()を使用して相互反発量の占有を解除しなければなりません。ロック()とアンロック()はペアで出現しなければなりません。try_ロック()は相互反発量をロックしようと試み、成功すればtrueに戻り、失敗すればfalseに戻り、ブロックではない。

#define _CRT_SECURE_NO_WARNINGS

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

std::mutex g_lock; //       ,#include <mutex>

//    
void printer(const char *str)
{
    g_lock.lock(); //  
    while(*str != '\0')
    {
        std::cout << *str;
        str++;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << std::endl;
    g_lock.unlock(); //   
}

//    
void func1()
{
    const char *str = "hello";
    printer(str);
}

//    
void func2()
{
    const char *str = "world";
    printer(str);
}


void mytest()
{
    std::thread t1(func1);
    std::thread t2(func2);

    t1.join();
    t2.join();

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
stdを使う:ロック(u)gurardはlock/unlockの書き方を簡単にすることができます。同時に安全です。gurardは構造時に、相互反発量を自動的にロックし、作用域を退出してから分析すると自動的にロックが解除され、unlock操作を忘れないようにします。

#define _CRT_SECURE_NO_WARNINGS

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

std::mutex g_lock; //       ,#include <mutex>

//    
void printer(const char *str)
{
    std::lock_guard<std::mutex> locker(g_lock); // lock_guard   
    while(*str != '\0')
    {
        std::cout << *str;
        str++;
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << std::endl;
    //         lock_guard      
}

//    
void func1()
{
    const char *str = "hello";
    printer(str);
}

//    
void func2()
{
    const char *str = "world";
    printer(str);
}


void mytest()
{
    std::thread t1(func1);
    std::thread t2(func2);

    t1.join();
    t2.join();

    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
原子操作
原子操作とは、「原子は最小で不可分の最小個体である」という意味で、複数のスレッドが同一のグローバルリソースにアクセスする際に、他のスレッドが全て同じ時間内に同じリソースにアクセスしないことを確認することができるという意味です。つまり、彼は同じ時間に唯一のスレッドだけがこのリソースにアクセスすることを確保しました。これは共有リソースへの相互反発対象のアクセスの保護に似ているが、原子操作はより低い層に近く、効率が高い。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <chrono>
#include <thread>

//       
long total = 0;

//    
void func()
{
    for(int i = 0;  i < 1000000; ++i)
    {
        //            
        total += 1;
    }
}


void mytest()
{
    clock_t start = clock();    //     

    //  
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    clock_t end = clock();    //     

    std::cout << "total = " << total << std::endl;
    std::cout << "time = " << end-start << " ms" << std::endl;


    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
スレッド間のデータ競争によって、運行毎に結果が異なります。したがって,データ競争の問題を防止するために,totalを原子操作する必要がある。



相互反発ロックによる原子操作:

#define _CRT_SECURE_NO_WARNINGS

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

std::mutex g_lock;

//       
long total = 0;

//    
void func()
{
    for(int i = 0;  i < 1000000; ++i)
    {
        g_lock.lock(); //   
        total += 1;
        g_lock.unlock(); //   
    }
}


void mytest()
{
    clock_t start = clock();    //     

    //  
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    clock_t end = clock();    //     

    std::cout << "total = " << total << std::endl;
    std::cout << "time = " << end-start << " ms" << std::endl;


    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
毎回運行の結果は同じです。時間がかかるだけです。


新しい標準C+11において,原子操作の概念を導入した。
複数のスレッドでこれらの種類の共有リソースを操作すると、コンパイラはこれらの動作が原子的であることを保証します。つまり、任意の時間に1つのスレッドだけがこのリソースにアクセスすることを保証します。コンパイラは複数のスレッドがこの共有リソースにアクセスする正確さを保証します。ロックの使用を回避し、効率を向上させました。

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <atomic>

//      
std::atomic<long> total(0); //      #include <atomic>

//    
void func()
{
    for(int i = 0;  i < 1000000; ++i)
    {
        // 
        total += 1;
    }
}


void mytest()
{
    clock_t start = clock();    //     

    //  
    std::thread t1(func);
    std::thread t2(func);

    t1.join();
    t2.join();

    clock_t end = clock();    //     

    std::cout << "total = " << total << std::endl;
    std::cout << "time = " << end-start << " ms" << std::endl;


    return;
}

int main()
{
    mytest();

    system("pause");
    return 0;
}
原子操作の実現は通常のデータ型と類似しているが,結果が正しいことを保証する前提で,mutexなどのロック機構よりも優れた性能を提供できる。
以上はc++11スレッドの相互反発量の詳細について説明しました。c+11スレッドの相互反発量に関する資料は他の関連記事に注目してください。