C++ マルチスレッド実装

C++ マルチスレッド実装

C++11 は言語レベルのマルチスレッド プログラミングをサポートし、プラットフォーム間で実行でき、Windows/Linux/Mac などをサポートします。

主に関与するもの:

  1. スレッド/ミューテックス/条件変数
  2. ロッククォード/ユニークロック
  3. 自動: アトミック タイプ、CAS 操作に基づくアトミック タイプ、スレッド セーフ
  4. 睡眠用

C++ のスレッドは基本的に、マルチスレッド用の Windows (createThread) や Linux (pthread_create) など、システムでサポートされている関数を呼び出します。

マルチスレッドについて理解する

  1. スレッドを作成して開始するにはどうすればよいですか? threadスレッド オブジェクトを作成するには、スレッド関数とスレッドに必要なパラメータが必要であり、スレッドは自動的に開かれます。
  2. 子スレッドはどのように終了しますか? サブスレッド関数の実行後、スレッドは終了します。
  3. メインスレッドは子スレッドをどのように処理するのでしょうか? joindetach方法。
#include <iostream>
#include <string>
#include<thread>

void threadHandle1() 
{
    
    
    // 让子线程睡眠两秒,this_thread获取当前线程,chrono计时的函数
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "hello,thread1" << std::endl;
}

void threadHandle2(int time)
{
    
    
    // 让子线程睡眠两秒,this_thread获取当前线程,chrono计时的函数
    std::this_thread::sleep_for(std::chrono::seconds(time));
    std::cout << "hello,thread2" << std::endl;
}

int main()
{
    
       
    // 创建了一个线程对象t1,传入一个线程函数,新线程就开始运行了
    std::thread t1(threadHandle1);
    // join是子线程等待主线程结束,主线程继续往下执行,
    // detach则是分离线程,子线程和主线程无关联,可以独立运行,等主线程结束,整个程序结束,所有子线程都自动结束了
    
    // 传入参数的情况
    std::thread t2(threadHandle2, 2);

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

    std::cout << "main thread hello" << std::endl;

    return 0;
}

ミューテックスとロック

マルチスレッド プログラムでは競合状態が発生することがあります。マルチスレッド プログラムの実行結果は一貫しており、CPU によるスレッドの異なる呼び出しシーケンスに従って異なる実行結果が生成されることはありません。したがって、複数のスレッド間のリソースアクセスの正確性を防ぐために、ミューテックスを導入する必要があります。

mutex初めて相互排他ロックについて知り、関数を使用してプロセス相互排他を完了すると、プログラムが中断され、ミューテックスのメモリ解放の問題が発生しますlockunlock具体的には次のようになります。

// 模拟车站卖票的程序
#include <iostream>
#include <string>
#include<thread>
#include<list>
#include<mutex>

using namespace std;
// 一共有tickCount张票
int tickCount = 10;
// 定义全局互斥锁
mutex mtx;

void sellTicket(int index)
{
    
    
    // mtx.lock(); //1、这样就只会存在一个窗口在卖票,因为while循环只支持一个线程访问
    while(tickCount>0)
    {
    
       
        /*
        直接使用mutex.lock()和mutex.unlock()将会出现一个问题,
        当函数还未unlock时就因为程序中间return或者error结束后,
        导致mutex资源无法释放的问题。
        */
        mtx.lock(); //2、仅在临界区代码段 -> 原子操作 -> 线程间互斥操作 -> mutex
        // 在锁里面加判断是防止当一个进程1面临tickCount=1时,还为-1成功,
        // 另一个线程2进入while循环,只是在mtx.lock阻塞了,等进程1-1成功后,
        // 其实进程2获取到的tickCount已经由1->0,但是还是会进行tickCount--
        // 导致了最终卖出-1张票
        if(tickCount>0)
        {
    
    
            cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
            tickCount--;
        }
        mtx.unlock(); //2、
        this_thread::sleep_for(chrono::milliseconds(2));
    }
    // mtx.unlock(); // 1、
}
/*
多线程程序:
竞态条件:多线程程序执行的结果是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。
*/
int main()
{
    
    
    list<thread> tlist;
    int thread_num = 3;
    for (int i = 0; i < thread_num;i++)
    {
    
    
        tlist.push_back(thread(sellTicket, i));
    }
    for (thread &t : tlist)
    {
    
    
        t.join();
    }

        return 0;
}

したがって、これはさらに導入されますlock_guard(関数パラメーターを渡したり返したりするプロセスでは使用できません。また、代入にも使用できません。単純にロックおよびロック解除されるクリティカルなコード セグメントでのみ使用できます) および (一般的には、次の目的で使用されます) unique_lock。プロセス通信、condition_varible共同使用) ロール内 スマートポインタと同様、フィールド終了後に自動的に破棄されます。次のコードに示すように

void sellTicket2(int index)
{
    
    
    while (tickCount > 0)
    {
    
    
        
        // mtx.lock(); 
        {
    
    
        	// lock_guard函数删除了拷贝构造函数和操作符=重载,类似于scoped_ptr,但保留了构造函数
            lock_guard<mutex> lock(mtx); //在这个局部作用域中,程序结束后自动析构,如果中间return了也会析构
            if (tickCount > 0)
            {
    
    
                cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
                tickCount--;
        }
        }
        // mtx.unlock(); 
        this_thread::sleep_for(chrono::milliseconds(2));
    }
}
void sellTicket3(int index)
{
    
    
    while (tickCount > 0)
    {
    
    

        // mtx.lock();
        {
    
    
        // lock_guard函数删除了拷贝构造函数和操作符=重载,类似于scoped_ptr,但保留了构造函数
        // lock_guard<mutex> lock(mtx); // 在这个局部作用域中,程序结束后自动析构,如果中间return了也会析构
        unique_lock<mutex> temp_lock(mtx); // 类似于unique_ptr,虽然删除了拷贝构造函数和操作符=重载,但是扩展了右值引用
        if (tickCount > 0)
        {
    
    
                cout << "窗口:" << index << "卖出第" << tickCount << "张票。" << endl;
                tickCount--;
        }
        }
        // mtx.unlock();
        this_thread::sleep_for(chrono::milliseconds(2));
    }
}

unique_lock以下の使用と組み合わせてcondition_variable:

mutex mtx;
condition_variable cv;

unique_lock<mutex> lck(mtx);
cv.wait(lck); //1、wait的作用使线程进入等待状态;2、lck.unlock可以把mtx给释放掉
// 通知cv上等待的线程,条件成立了,可以往下运行了
//其他在cv上等待的线程,收到通知,从等待状态->阻塞状态->获取互斥锁->线程执行
cv.notify_all();

スレッド同期通信

マルチスレッド プログラミングの問題:

  1. スレッド間の相互排他により、リソースへのアクセスの問題を防ぎます。競合状態 -> クリティカル セクション コード セグメント -> アトミック操作 -> 相互排他ロック ミューテックス (lock_guard、unique_lock) / CAS の強力な 2 レベル ロックフリー実装
  2. スレッド間の同期通信。プロデューサとコンシューマのスレッド モデル。

相互排他的

この共有変数のコードをマルチスレッドで実行すると競合状態が発生する可能性があるため、このコードをクリティカル セクションと呼びます。これは、共有リソースを実行するコードの一部であり、複数のスレッドで同時に実行してはなりません。

したがって、このコードが相互に排他的 (相互排他) であること、つまり、1 つのスレッドだけがクリティカル セクションのコード セグメントを実行でき、他のスレッドはブロックされてキュー効果が得られるまで待機していることを望みます。

相互排他は、マルチスレッドの競合状態だけでなく、共有リソースの混乱を避けるためにマルチプロセスでも行われます。

同期する

相互排他は、クリティカルセクションを使用する「複数のプロセス/スレッド」の問題を解決しますが、「複数のプロセス/スレッド」が連携する問題は解決しません。

マルチスレッドでは、各スレッドが順番に実行される必要があることは誰もが知っています。各スレッドは独立しており、予測できない速度で進行しますが、場合によっては、複数のスレッドが緊密に連携して共通のタスクを達成できることを期待することがあります。

いわゆる同期とは、「複数のプロセス/スレッド」がいくつかの重要なポイントで相互に待機し、通信する必要があることを意味します。この待機および情報の通信の相互制限は、「プロセス/スレッド」同期と呼ばれます。

プロデューサ、コンシューマのスレッド通信

これは 2 つのスレッド間の通信ですが、プロデューサーとコンシューマーのスレッドは互いに通知し合い、lock_guardプロセス間通信のような複雑な作業を実現することは不可能なので、使用unique_lockしてcondition_variable一致させることでプロセス間通信を実現できます。コードは次のようになります。

#include <iostream>
#include <string>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<queue>

using namespace std;

// 定义互斥锁,用户线程间互斥
mutex mtx;
// 定义条件变量,用于线程间的同步通信
condition_variable cv;

// 最常见的问题就是消费者线程消费的更快,生产者线程还没生产出来就开始消费了
class Queue
{
    
    
    public:
    void put(int val)
    {
    
    
        // lock_guard<mutex> lock(mtx);

        unique_lock<mutex> lck(mtx);
        while(!que.empty())
        {
    
    
            // que不为空,生产者应该通知消费者去消费,消费完了在生产
            // 生产者进程应该进入阻塞状态,并把mtx互斥锁
            cv.wait(lck);
        }
        que.push(val);
        /*
        notify_one:通知另外的一个线程
        notify_all:通知另外的所有线程
        */
        // 通知其他的所有线程,生产了一个物品,可以进行消费了,
        // 其他线程得到该通知就会从 等待状态 -> 阻塞状态 -> 获取互斥锁才能继续执行。
        cv.notify_all(); 
        cout << "生产者 生产:" << val << "号物品" << endl;
    }
    int get()
    {
    
    
        // lock_guard<mutex> lock(mtx);
        unique_lock<mutex> lck(mtx);
        while(que.empty())
        {
    
    
            // 消费者发现que是空的,通知生产者线程生产物品
            // 进入等待状态,把互斥锁mutex进行释放
            cv.wait(lck);
        }
        int val = que.front();
        que.pop();
        cv.notify_all(); //消费完了,通知其他线程进行生产
        cout << "消费者 消费:" << val << "号物品" << endl;
        return val;
    }
    private:
        queue<int> que;
};
void producer(Queue* que)
{
    
    
    for (int i = 0; i <= 10;i++)
    {
    
    
        que->put(i);
        this_thread::sleep_for(chrono::milliseconds(100));
    }
}
void consumer(Queue* que)
{
    
    
    for (int i = 0; i <= 10; i++)
    {
    
    
        que->get();
        this_thread::sleep_for(chrono::milliseconds(100));
    }
}
int main()
{
    
    
    Queue que;
    thread t1(producer, &que);
    thread t2(consumer,&que);

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

    return 0;
}

CAS操作

ミューテックスロックは比較的重いので、クリティカルセクションのコードが複雑なことを行う場合、ミューテックスロックを使用するのは非常に面倒です。ただし、一部のコード操作のアトミックな性質を理解するには CAS を使用するだけで十分であり、CAS にはロックがありません。使用されるヘッダー ファイルはatomic本質的に、特定の型をアトミック型変数として設定するため、独立して使用できるスレッドは 1 つだけになります。次の例に示すように:

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

using namespace std;

/*
使用lock_guard实现临界代码段的互斥访问
lock_guard<mutex> lock(mtx);
Count++;
*/
volatile std::atomic_bool isReady = {
    
    false};
volatile std::atomic_int number = {
    
    0};

void task()
{
    
    
    while(!isReady)
    {
    
    
        // 让线程让出当前的CPU时间片,等待下一次调度
        this_thread::yield();
    }
    for (int i = 0; i < 100;i++)
    {
    
    
        number++;
    }
}
int main()
{
    
    
    list<thread> tlist;
    for (int i = 0; i < 10;i++)
    {
    
    
        tlist.push_back(thread(task));
    }

    // 让主线程睡眠三秒
    this_thread::sleep_for(chrono::seconds(3));
    cout << "number = " << number << endl;
    isReady = true;
    cout << "number = " << number << endl;
    for(thread &t:tlist)
    {
    
    
        t.join();
    }
    cout << "number = " << number << endl;
    return 0;
}
/*output::
number = 0
number = 1000
number = 1000
*/

おすすめ

転載: blog.csdn.net/qq_45041871/article/details/131939910