C++機能のマルチスレッド化について詳しく解説

1.マルチスレッド化

従来の C++ (C++11 より前) ではスレッドの概念が導入されていませんでした。C++11 が登場する前は、C++ でマルチスレッドを実装したい場合は、Linux などのオペレーティング システム プラットフォームによって提供される API を使用する必要がありました。 < pthread.h>、または Windows の <windows.h>。

C++11 は言語レベルでマルチスレッドを提供します。これはヘッダー ファイル <thread> に含まれています。クロスプラットフォームの問題を解決し、 を提供します管理线程、保护共享数据、线程间同步操作、原子操作等类新しい C++11 標準では、次の図に示すように、マルチスレッド プログラミングをサポートするために 5 つのヘッダー ファイルが導入されています。
ここに画像の説明を挿入

1.1 マルチプロセスとマルチスレッド

  • 複数プロセスの同時実行

マルチプロセス同時実行の使用は、アプリケーションを複数の独立したプロセス (各プロセスにはスレッドが 1 つだけある) に分割することであり、これらの独立したプロセスは相互に通信してタスクを一緒に完了できます。オペレーティング システムはプロセスに多数の保護メカニズムを提供するため、以避免一个进程修改了另一个进程的数据,使用多进程比多线程更容易写出安全的代码. ただし、これにより、マルチプロセスの同時実行の 2 つの欠点も生まれます。

  1. 比较复杂プロセス間通信では、シグナル、ソケット、ファイル、パイプなどを使用するかどうかに関係なく、いずれか、または両方が使用されます速度较慢
  2. 複数のスレッドを実行するには开销很大、オペレーティング システムが実行する必要があります分配很多的资源来对这些进程进行管理

複数のプロセスが同じタスクを同時に完了すると、次のことが避けられません操作同一个数据和进程间的相互通信,上述的两个缺点也就决定了多进程的并发不是一个好的选择

  • マルチスレッドの同時実行

マルチスレッド同時実行とは、同じプロセス内で複数のスレッドを実行することを指します。

利点:

オペレーティング システムに関する知識がある人は、线程是轻量级的进程各スレッドが異なる命令シーケンスを独立して実行できることを知っているはずですが、スレッドはリソースを独立して所有するのではなく、それを作成したプロセスに依存します。つまり、同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递この方法では、同じプロセス内の複数のスレッドがデータを簡単に共有して通信できるため、プロセスよりも同時操作に適しています。

短所:

オペレーティング システムによって提供される保護メカニズムが欠如しているため、マルチスレッドがデータを共有して通信する場合、程序员做更多的工作共有データ セグメントに対する操作が予想される操作順序で実行されることを保証し、デッドロックを回避する必要があります。 (デッドロック)可能な限り。

「C++ 同時プログラミング」からの抜粋

1.2 マルチスレッドの理解

  • 単一の CPU コア上の複数のスレッド。

1 つのタイム スライスでは 1 つのスレッドのコードが実行されますが、これは真の並列コンピューティングではありません。
ここに画像の説明を挿入

  • 複数の CPU または複数のコア

本格的な並列計算が可能です。
ここに画像の説明を挿入

1.3 スレッドの作成

スレッドの作成は非常に簡単で、スレッドに関数を追加するだけです。

  • フォーム 1:
std::thread myThread ( thread_fun);//函数形式为void thread_fun()
myThread.join();
//同一个函数可以代码复用,创建多个线程
  • フォーム 2:
std::thread myThread ( thread_fun(100));
myThread.join();
//函数形式为void thread_fun(int x)
//同一个函数可以代码复用,创建多个线程
  • フォーム 3:
std::thread (thread_fun,1).detach();//直接创建线程,没有名字
//函数形式为void thread_fun(int x)
  • コード例
#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    
    
    cout<<"子线程1"<<endl;
}
void thread_2(int x)
{
    
    
    cout<<"x:"<<x<<endl;
  cout<<"子线程2"<<endl;
}
int main()
{
    
    
  thread first ( thread_1);     // 开启线程,调用:thread_1()
  thread second (thread_2,100);  // 开启线程,调用:thread_2(100)
  //thread third(thread_2,3);//开启第3个线程,共享thread_2函数。
  std::cout << "主线程\n";

  first.join(); //必须说明添加线程的方式            
  second.join(); 
  std::cout << "子线程结束.\n";//必须join完成
  return 0;
}

1.4 結合および分離メソッド

スレッドが開始された後、そのスレッドに関連付けられたスレッドが破棄される前に、スレッドの実行の終了を待つ方法を決定する必要があります。上の例の結合など。

  • デタッチ モードでは、開始されたスレッドはバックグラウンドで独立して実行され、現在のコードは新しいスレッドの終了を待たずに実行を続けます。
  • join メソッドは、開始されたスレッドが完了するまで待機してから、実行を続行します。

joinable を使用すると、参加モードか分離モードかを判断できます。

if (myThread.joinable()) foo.join();

(1) 結合例

次のコードでは、子スレッドが終了するまでjoin後のコードは実行されません。

#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    
    
    while(1)
    {
    
    
        //cout<<"子线程1111"<<endl;
    }
}
void thread_2(int x)
{
    
    
    while(1)
    {
    
    
        //cout<<"子线程2222"<<endl;
    }
}
int main()
{
    
    
    thread first ( thread_1);     // 开启线程,调用:thread_1()
    thread second (thread_2,100);  // 开启线程,调用:thread_2(100)

    first.join();                // pauses until first finishes 这个操作完了之后才能destroyed
    second.join();               // pauses until second finishes//join完了之后,才能往下执行。
    while(1)
    {
    
    
        std::cout << "主线程\n";
    }
    return 0;
}

(2) デタッチの例

次のコードでは、メインスレッドは子スレッドの終了を待ちません。メインスレッドの実行が終了すると、プログラムは終了します。

#include <iostream>
#include <thread>
using namespace std;
void thread_1()
{
    
    
    while(1)
    {
    
    
        cout<<"子线程1111"<<endl;
    }
}
void thread_2(int x)
{
    
    
    while(1)
    {
    
    
        cout<<"子线程2222"<<endl;
    }
}
int main()
{
    
    
    thread first ( thread_1);     // 开启线程,调用:thread_1()
    thread second (thread_2,100);  // 开启线程,调用:thread_2(100)

    first.detach();                
    second.detach();            
    for(int i = 0; i < 10; i++)
    {
    
    
        std::cout << "主线程\n";
    }
    return 0;
}

this_thread は、次の 4 つの関数を持つクラスです。

関数 使用 説明する
get_id std::this_thread::get_id() スレッドIDを取得する
収率 std::this_thread::yield() スレッドの実行を放棄し、準備完了状態に戻ります。
睡眠用 std::this_thread::sleep_for(std::chrono::seconds(1)); 1秒間一時停止します
寝るまで 次のように 1分後に次のように実行します
using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t(system_clock::now());

struct std::tm * ptm = std::localtime(&tt);
cout << "Waiting for the next minute to begin...\n";
++ptm->tm_min; //加一分钟
ptm->tm_sec = 0; //秒数设置为0
//暂停执行,到下一整分执行
this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));

2.ミューテックス

ミューテックス ヘッダー ファイルは主にミューテックスに関連するクラスを宣言します。mutex は、次の表に示すように 4 種類の相互排他を提供します。

タイプ 説明する
std::ミューテックス 最も基本的な Mutex クラス。
std::recursive_mutex 再帰的なミューテックス クラス。
std::time_mutex 時限ミューテックス クラス。
std::recursive_timed_mutex 時間指定された再帰的な Mutex クラス。

std::mutex は C++11 の最も基本的なミューテックスです。std::mutex オブジェクトは排他的所有権の特性を提供します。つまり、std::mutex オブジェクトの再帰的ロックをサポートしませんが、std::recursive_lock Mutex オブジェクトは再帰的にロックできますか。

2.1 ロックとロック解除

ミューテックスの一般的な操作:

  • lock(): リソースロック
  • lock(): リソースのロックを解除します
  • trylock(): ロックされているかどうかを確認します。次の 3 種類があります。

(1) ロックされていない場合は false を返してロックする、
(2) 他のスレッドがロックしている場合は true を返す、
(3) 既に同じスレッドがロックしている場合はデッドロックが発生します。

デッドロック:実行中に複数のプロセスがリソースの奪い合いや通信などによりブロックされ、外力がなければ先に進めなくなる現象を指します。このとき、システムがデッドロック状態にある、またはシステム内でデッドロックが発生しているといい、このように常に待ち続けているプロセスをデッドロックプロセスと呼びます。

以下にロックとロック解除を例を挙げて説明します。

同じミューテックス変数がロックされると、一定期間内にその変数にアクセスできるのは 1 つのスレッドだけになります。例えば:

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

std::mutex mtx;           // mutex for critical section

void print_block (int n, char c) {
    
    
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx.lock();
  for (int i=0; i<n; ++i) {
    
     std::cout << c; }
  std::cout << '\n';
  mtx.unlock();
}

int main ()
{
    
    
  std::thread th1 (print_block,50,'*');//线程1:打印*
  std::thread th2 (print_block,50,'$');//线程2:打印$

  th1.join();
  th2.join();

  return 0;
}

出力:

**************************************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

それらが異なるミューテックス変数である場合、同じリソースに対する競合が発生しないため、次のコードが交互に出力されるか、別のスレッドが共通のグローバル変数を変更する可能性があります。:

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

std::mutex mtx_1;           // mutex for critical section
std::mutex mtx_2;           // mutex for critical section

int test_num = 1;

void print_block_1 (int n, char c) {
    
    
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx_1.lock();
  for (int i=0; i<n; ++i) {
    
    
      //std::cout << c;
      test_num = 1;
      std::cout<<test_num<<std::endl;
  }
  std::cout << '\n';
  mtx_1.unlock();
}
void print_block_2 (int n, char c) {
    
    
  // critical section (exclusive access to std::cout signaled by locking mtx):
  mtx_2.lock();
  test_num = 2;
  for (int i=0; i<n; ++i) {
    
    
      //std::cout << c;
      test_num = 2;
      std::cout<<test_num<<std::endl;
  }
  mtx_2.unlock();
}

int main ()
{
    
    
  std::thread th1 (print_block_1,10000,'*');
  std::thread th2 (print_block_2,10000,'$');

  th1.join();
  th2.join();

  return 0;
}

2.2 ロックガード

lock_guard オブジェクトが作成されると、それに提供されたミューテックスの所有権を取得しようとします。制御フローが lock_guard オブジェクトのスコープを離れると、lock_guard はミューテックスを破棄して解放します。

lock_guard の特徴:

  • 作成時にロックし、スコープが終了すると自動的に破棄してロックを解除します。手動でロックを解除する必要はありません。
  • 途中でロックを解除することはできません。ロックを解除する前にスコープが終了するまで待つ必要があります。
  • コピーできません

コード例

#include <thread>
#include <mutex>
#include <iostream>

int g_i = 0;
std::mutex g_i_mutex;  // protects g_i,用来保护g_i

void safe_increment()
{
    
    
    const std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
    // g_i_mutex自动解锁
}

int main()
{
    
    
	std::cout << "main id: " <<std::this_thread::get_id()<<std::endl;
    std::cout << "main: " << g_i << '\n';

    std::thread t1(safe_increment);
    std::thread t2(safe_increment);

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

    std::cout << "main: " << g_i << '\n';
}

例証します:

  1. このプログラムの機能は、スレッドが通過するたびに g_i に 1 を加算することです。
  2. 共通リソース g_i が関係するため、共通ミューテックス g_i_mutex が必要です。
  3. メインスレッドのIDは1なので、次のスレッドのIDは順番に1ずつ増えていきます。

2.3 unique_lock

簡単に言うと、 unique_lock は lock_guard のアップグレードおよび強化されたバージョンであり、lock_guard のすべての機能を備え、その他の多くのメソッドを備えており、より柔軟で使いやすく、より複雑なロックのニーズを満たすことができます。

Unique_lock の機能:

  • (2 番目のパラメーターを std::defer_lock として指定することにより) ロックせずに作成し、必要に応じてロックすることができます。
  • いつでも施錠・解錠可能
  • スコープのルールはlock_grardと同じで、ロックが破棄されると自動的に解放されます。
  • コピーはできませんが、移動は可能です
  • 条件変数にはパラメータとしてこのタイプのロックが必要です (この場合は unique_lock を使用する必要があります)

lock_guard で実行できることはすべて、unique_lock を使用して実行でき、その逆も同様です。では、いつ lock_guard を使用するのでしょうか? 非常に簡単です。ロックを使用する必要がある場合は、lock_guard が最も単純なロックであるため、最初に lock_guard の使用を検討します。

コード例は次のとおりです。

#include <mutex>
#include <thread>
#include <iostream>
struct Box {
    
    
    explicit Box(int num) : num_things{
    
    num} {
    
    }

    int num_things;
    std::mutex m;
};

void transfer(Box &from, Box &to, int num)
{
    
    
    // defer_lock表示暂时unlock,默认自动加锁
    std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
    std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);

    //两个同时加锁
    std::lock(lock1, lock2);//或者使用lock1.lock()

    from.num_things -= num;
    to.num_things += num;
    //作用域结束自动解锁,也可以使用lock1.unlock()手动解锁
}

int main()
{
    
    
    Box acc1(100);
    Box acc2(50);

    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

    t1.join();
    t2.join();
    std::cout << "acc1 num_things: " << acc1.num_things << std::endl;
    std::cout << "acc2 num_things: " << acc2.num_things << std::endl;
}

例証します:

  1. この関数の機能は、ある構造体の変数から num を減算し、それを別の構造体の変数にロードすることです。
  2. std::mutex m; この構造では、ミューテックスは共有されません。ただし、参照が渡された後、同じロックが 2 つの関数に渡されるため、ロックできるのは 1 つのロックだけです。
  3. cout は結合後に実行する必要があります。そうしないと、cout の結果が最終結果になるとは限りません。
  4. std::ref は、参照によって渡された値をラップするために使用されます。
  5. std::cref は、const 参照で渡された値をラップするために使用されます。

3. 条件変数

condition_variable のヘッダー ファイルには 2 つの変数クラスがあり、1 つはcondition_variable で、もう 1 つはcondition_variable_any です。condition_variable は unique_lock と組み合わせて使用​​する必要があります。condition_variable_any は任意のロックを使用できます。以下では、condition_variable を例として取り上げます。

condition_variable 条件変数は、 (notify_one または Notice_all) を使用して通知が再開されるまで、呼び出しスレッドをブロック (wait、wait_for、wait_until) できます条件変数はクラスであり、このクラスはコンストラクタとデストラクタの両方を持っており、使用する場合は、対応する条件変数オブジェクトを構築し、そのオブジェクトの対応する関数を呼び出すことで上記の機能を実現します。

タイプ 説明する
条件変数 オブジェクトを構築する
破壊する 消去
待って 通知されるまで待ちます
を待つ タイムアウトになるか通知されるまで待ちます
wait_until 通知または時点まで待ちます
通知ワン 1 つのスレッドのロックを解除します。複数のスレッドがある場合、どのスレッドが実行されるかは不明です
通知すべて すべてのスレッドのロックを解除する
cv_status 以下に示すように、変数の状態を表すクラスです。
enum class cv_status {
    
     no_timeout, timeout };

3.1 待つ

現在のスレッドは、wait() を呼び出した後 (この時点で現在のスレッドはロック (ミューテックス) を取得しているはずです。ロック lck を設定することもできます)、別のスレッドが Notice_* を呼び出して現在のスレッドをウェイクアップするまで、ブロックされます。

スレッドがブロックされると、この関数は自動的に lck.unlock() を呼び出してロックを解放し、ロック競合でブロックされている他のスレッドが実行を継続できるようにしますさらに、現在のスレッドが通知されると (通知されると、通常、別のスレッドが現在のスレッドをウェイクアップするために、notify_* を呼び出します)、wait() 関数も自動的に lck.lock() を呼び出し、lck の状態は次と同じになります。 wait関数が呼び出されたとき。

コード例:

#include <iostream>           // std::cout
#include <thread>             // std::thread, std::this_thread::yield
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;
bool shipment_available() {
    
    return cargo!=0;}

void consume (int n) {
    
    
    for (int i=0; i<n; ++i) {
    
    
        std::unique_lock<std::mutex> lck(mtx);//自动上锁
        //第二个参数为false才阻塞(wait),阻塞完即unlock,给其它线程资源
        cv.wait(lck,shipment_available);
        // consume:
        std::cout << cargo << '\n';
        cargo=0;
    }
}

int main ()
{
    
    
    std::thread consumer_thread (consume,10);

    for (int i=0; i<10; ++i) {
    
    
        //每次cargo每次为0才运行。
        while (shipment_available()) std::this_thread::yield();
        std::unique_lock<std::mutex> lck(mtx);
        cargo = i+1;
        cv.notify_one();
    }

    consumer_thread.join();,
    return 0;
}

例証します:

  1. メインスレッドの while は、cargo=0 の場合にのみ実行されます。
  2. カーゴが 0 に設定されるたびに、子スレッドはブロックを解除する (非ブロック化) ように通知されます。つまり、子スレッドは実行を継続できます。
  3. 子スレッドのカーゴが 0 に設定された後、wait は再び待機を開始します。つまり、shipping_available が false の場合は、待機します。

3.2 待機

std::condition_variable::wait() と似ていますが、wait_for は期間を指定でき、現在のスレッドが通知を受信するか、指定された時間 rel_time がタイムアウトになるまで、スレッドはブロックされます。タイムアウトまたは他のスレッドからの通知を受信すると、wait_for が戻り、残りの処理手順は wait() と同様になります。

template <class Rep, class Period>
  cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time);

さらに、wait_for のオーバーロードされたバージョンの最後のパラメータ pred は wait_for の予測条件を表します。pred 条件が false の場合にのみ、wait() の呼び出しは現在のスレッドをブロックします。また、他のスレッドからの通知を受信した後、pred の場合にのみ、wait() の呼び出しが現在のスレッドをブロックします。 true の場合はブロックが解除されます。

template <class Rep, class Period, class Predicate>
       bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred);

コード例:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>             // std::chrono::seconds
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status

std::condition_variable cv;

int value;

void read_value() {
    
    
  std::cin >> value;
  cv.notify_one();
}

int main ()
{
    
    
  std::cout << "Please, enter an integer (I'll be printing dots): \n";
  std::thread th (read_value);

  std::mutex mtx;
  std::unique_lock<std::mutex> lck(mtx);
  while (cv.wait_for(lck,std::chrono::seconds(1))==std::cv_status::timeout) {
    
    
    std::cout << '.' << std::endl;
  }
  std::cout << "You entered: " << value << '\n';

  th.join();

  return 0;
}

  1. 通知またはタイムアウトのロックが解除されるため、メインスレッドは出力を続けます。
  2. この例では、1 秒が経過する限り印刷を続けます。

4. スレッドプール

4.1 コンセプト

プログラムでスレッドを複数回使用する必要がある場合、スレッドの作成と破棄を複数回行う必要があることを意味します。スレッドの作成と破棄のプロセスでは必然的にメモリが消費され、スレッドが多すぎると動員のオーバーヘッドが発生し、結果的にキャッシュの局所性と全体的なパフォーマンスに影響を与えます。

スレッドの作成と破棄には次のような欠点があります。

  • 作成するスレッドが多すぎると、特定のリソースが無駄になり、一部のスレッドが完全に使用されなくなります。
  • 多くのスレッドを破棄すると、後で再度スレッドを作成する時間が無駄になります。
  • スレッドの作成が遅すぎると、待機時間が長くなり、パフォーマンスが低下します。
  • スレッドの破棄が遅すぎるため、他のスレッドのリソースが不足します。

スレッド プールは複数のスレッドを維持するため、短期タスクの処理時にスレッドを作成および破棄するコストが回避されます。

4.2 スレッドプールの実装

プログラムの実行中にスレッドを作成するには時間がかかるため、プログラムの実行前に複数のスレッドを作成し、プログラムの実行中にスレッドから使​​用するだけで済むようにするプーリングの考えを採用します。プール .プログラムの動作効率が大幅に向上しました。

一般的なスレッド プールは次の部分で構成されます。

  1. スレッド プール マネージャー (ThreadPoolManager): スレッド プール、つまりスレッド プール クラスの作成と管理に使用されます。
  2. ワークスレッド (WorkThread):スレッドプール内のスレッド
  3. タスクキュータスク:未処理のタスクを保存するために使用されます。バッファリングメカニズムを提供します。
  4. append: タスクを追加するためのインターフェース

スレッド プールの実装コード:

#ifndef _THREADPOOL_H
#define _THREADPOOL_H
#include <vector>
#include <queue>
#include <thread>
#include <iostream>
#include <stdexcept>
#include <condition_variable>
#include <memory> //unique_ptr
#include<assert.h>

const int MAX_THREADS = 1000; //最大线程数目

template <typename T>
class threadPool
{
    
    
public:
    threadPool(int number = 1);//默认开一个线程
    ~threadPool();
    std::queue<T *> tasks_queue;		   //任务队列

    bool append(T *request);//往请求队列<task_queue>中添加任务<T *>

private:
    //工作线程需要运行的函数,不断的从任务队列中取出并执行
    static void *worker(void *arg);
    void run();

private:
    std::vector<std::thread> work_threads; //工作线程

    std::mutex queue_mutex;
    std::condition_variable condition;  //必须与unique_lock配合使用
    bool stop;
};//end class

//构造函数,创建线程
template <typename T>
threadPool<T>::threadPool(int number) : stop(false)
{
    
    
    if (number <= 0 || number > MAX_THREADS)
        throw std::exception();
    for (int i = 0; i < number; i++)
    {
    
    
        std::cout << "created Thread num is : " << i <<std::endl;
        work_threads.emplace_back(worker, this);//添加线程
        //直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
    }
}
template <typename T>
inline threadPool<T>::~threadPool()
{
    
    

    std::unique_lock<std::mutex> lock(queue_mutex);
    stop = true;

    condition.notify_all();
    for (auto &ww : work_threads)
        ww.join();//可以在析构函数中join
}
//添加任务
template <typename T>
bool threadPool<T>::append(T *request)
{
    
    
    /*操作工作队列时一定要加锁,因为他被所有线程共享*/
    queue_mutex.lock();//同一个类的锁
    tasks_queue.push(request);
    queue_mutex.unlock();
    condition.notify_one(); //线程池添加进去了任务,自然要通知等待的线程
    return true;
}
//单个线程
template <typename T>
void *threadPool<T>::worker(void *arg)
{
    
    
    threadPool *pool = (threadPool *)arg;
    pool->run();//线程运行
    return pool;
}
template <typename T>
void threadPool<T>::run()
{
    
    
    while (!stop)
    {
    
    
        std::unique_lock<std::mutex> lk(this->queue_mutex);
        /* unique_lock() 出作用域会自动解锁 */
        this->condition.wait(lk, [this] {
    
     return !this->tasks_queue.empty(); });
        //如果任务为空,则wait,就停下来等待唤醒
        //需要有任务,才启动该线程,不然就休眠
        if (this->tasks_queue.empty())//任务为空,双重保障
        {
    
    
            assert(0&&"断了");//实际上不会运行到这一步,因为任务为空,wait就休眠了。
            continue;
        }
        else
        {
    
    
            T *request = tasks_queue.front();
            tasks_queue.pop();
            if (request)//来任务了,开始执行
                request->process();
        }
    }
}
#endif

例証します:

  • コンストラクターの作成に必要なスレッドの数
  • スレッドはタスクに対応し、タスクはいつでも完了する可能性があり、スレッドはスリープ状態になる可能性があるため、タスクはキューを使用して実装され (スレッドの数は制限されています)、スレッドは待機メカニズムを使用します。
  • タスクは常に追加されており、その数はスレッドの数を超える場合があり、キューの先頭にあるタスクが最初に実行されます。
  • タスク (追加) が追加された後でのみ、スレッドのcondition.notify_one() が開始されます。
  • wait は、タスクが空の場合、スレッドがスリープ状態になり、新しいタスクの追加を待つことを意味します。
  • リソースが共有されるため、タスクを追加するときにロックを追加する必要があります。

テストコード:

#include "mythread.h"
#include<string>
#include<math.h>
using namespace std;
class Task
{
    
    
    public:
    void process()
    {
    
    
        //cout << "run........." << endl;
        //测试任务数量
        long i=1000000;
        while(i!=0)
        {
    
    
            int j = sqrt(i);
            i--;
        }
    }
};
int main(void)
{
    
    
    threadPool<Task> pool(6);//6个线程,vector
    std::string str;
    while (1)
    {
    
    
            Task *tt = new Task();
            //使用智能指针
            pool.append(tt);//不停的添加任务,任务是队列queue,因为只有固定的线程数
            cout<<"添加的任务数量: "<<pool.tasks_queue.size()<<endl;;
            delete tt;
    }
}

参考:

C++11 スレッド プールは github を実装します: https://github.com/progschj/ThreadPool

C++11 スレッド プールの 2 つの実装: https://blog.csdn.net/liushengxi_root/article/details/83932654

アトミック操作(相互排除に似ていますが、基礎となる実装に近く、より効率的です。まだ更新されていません)

おすすめ

転載: blog.csdn.net/yohnyang/article/details/131499421