如何在 C++ 中为需要访问资源池中的两个不同资源的线程管理线程同步

How to manage thread synchronization in C++ for threads which need access to two different resources from a pool of resources

提问人:Nicolas Cinq-Mars 提问时间:10/27/2023 最后编辑:Nicolas Cinq-Mars 更新时间:10/28/2023 访问量:88

问:

该程序模拟访问不同资源的多个线程。 我有一个资源池,在我的情况下是一个布尔数组: 我还创建了 6 个不同的线程,它们都需要一次访问其中两个资源:bool res[6] = {1, 1, 1, 1, 1, 1};

T1 -> needs res[0] and res[1]
T2 -> needs res[1] and res[2]
T3 -> needs res[2] and res[3]
T4 -> needs res[3] and res[4]
T5 -> needs res[4] and res[5]
T6 -> needs res[0] and res[5]

当线程使用资源时,它会将布尔值设置为 0,并在完成后恢复为 1。例如:T1 将 res[0] 和 res[1] 设置为 0,“使用它”,然后将两者设置回 1。

我不确定如何使用互斥锁或信号量来防止竞争条件。

显而易见但缓慢的方法是,当线程使用两个资源时,使用互斥锁来锁定整个部分,并在完成后将其解锁,从而防止任何其他线程占用任何资源。这样做的问题是一些线程应该能够同时运行,例如:T1 和 T3 不共享共同的资源,因此应该能够同时执行这些操作。另一方面,T1 和 T2 在共享 res[1] 时不应该同时运行。任何帮助都是值得赞赏的!

编辑:这是我尝试过为每个资源使用信号量的东西,我认为这个解决方案有效,但我可能是错的:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <semaphore>
#include <chrono>
#include <syncstream>

bool el1[6]={1,1,1,1,1,1};

const int64_t tempsCreationObjetsMS = 3000;

std::binary_semaphore sem0{0}, sem1{0}, sem2{0}, sem3{0}, sem4{0}, sem5{0};

void creer_thread(std::string nom)
{
    if(nom == "coupe")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;
    
        if (el1[0] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 0" << std::endl;
            sem0.acquire();
        }
        if (el1[1] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 1" << std::endl;
            sem1.acquire();
        }

        el1[0] = 0;
        el1[1] = 0;

        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;

        el1[0] = 1;
        el1[1] = 1;

        sem0.release();
        sem1.release();
    }
    else if(nom=="epee")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

        if (el1[1] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 1" << std::endl;
            sem1.acquire();
        }
        if (el1[2] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 2" << std::endl;
            sem2.acquire();
        }

        el1[1] = 0;
        el1[2] = 0;

        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;
        el1[1] = 1;
        el1[2] = 1;

        sem1.release();
        sem2.release();
    }
    else if(nom=="chandelier")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

        if (el1[2] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 2" << std::endl;
            sem2.acquire();
        }
        if (el1[3] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 3" << std::endl;
            sem3.acquire();
        }

        el1[2] = 0;
        el1[3] = 0;
        
        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;

        el1[2] = 1;
        el1[3] = 1;

        sem2.release();
        sem3.release();
    }
    else if(nom=="bague")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

        if (el1[3] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 3" << std::endl;
            sem3.acquire();
        }
        if (el1[4] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 4" << std::endl;
            sem4.acquire();
        }

        el1[3] = 0;
        el1[4] = 0;

        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;

        el1[3] = 1;
        el1[4] = 1;

        sem3.release();
        sem4.release();
    }
    else if(nom=="table")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

        if (el1[4] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 4" << std::endl;
            sem4.acquire();
        }
        if (el1[5] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 5" << std::endl;
            sem5.acquire();
        }

        el1[4] = 0;
        el1[5] = 0;

        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;

        el1[4] = 1;
        el1[5] = 1;

        sem4.release();
        sem5.release();
    }
    else if(nom=="porte")
    {
        std::osyncstream(std::cout) << "Je suis le thread " << nom << std::endl;

        if (el1[0] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 0" << std::endl;
            sem0.acquire();
        }
        if (el1[5] != 1)
        {
            std::osyncstream(std::cout) << nom << " en attente de la resource 5" << std::endl;
            sem5.acquire();
        }

        el1[0] = 0;
        el1[5] = 0;
        
        std::osyncstream(std::cout) << nom << " en creation." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(tempsCreationObjetsMS));
        std::osyncstream(std::cout) << nom << " cree." << std::endl;

        el1[0] = 1;
        el1[5] = 1;

        sem0.release();
        sem5.release();
    }
}

int main(void)
{
    std::thread th1(creer_thread,"coupe");
    std::thread th2(creer_thread,"epee");
    std::thread th3(creer_thread,"chandelier");
    std::thread th4(creer_thread,"bague");
    std::thread th5(creer_thread,"table");
    std::thread th6(creer_thread,"porte");

    th1.join();
    th2.join();
    th3.join();
    th4.join();
    th5.join();
    th6.join();
}
C++ 同步 线程安全

评论

1赞 Scheff's Cat 10/27/2023
这听起来有点像 Dining Philosophers(除了通常只有 5 个,但这并没有太大变化)。
2赞 Alan Birtles 10/27/2023
每个资源互斥锁和scoped_lock可能是最简单的
0赞 Scheff's Cat 10/27/2023
您可以使用 s 或 s 代替 muteces。从 C++20 开始,甚至还有 a 和 / 。std::atomic<bool>std::atomic_flagwait()notify_one()notify_all()
1赞 user207421 10/27/2023
你可以有六个锁,只要你总是以相同的顺序获取它们:否则,死锁。
2赞 Scheff's Cat 10/27/2023
@user207421只要您始终以相同的顺序获取它们或使用 std::lock 获取它们。

答:

0赞 dakotac 10/27/2023 #1

理想的解决方案取决于资源的性质,但可以使用信号量或互斥锁,速度更多地取决于实现。

我建议使用互斥锁,因为听起来每个资源都是唯一的,每个线程都需要等待特定的资源。每个资源都需要自己的互斥锁来控制线程对它的访问。这样,就不需要锁定其他线程,除非它们想要使用该特定资源。

例如:线程 1 正在使用资源 1,因此它将锁定资源 1 的互斥锁。如果任何其他线程想要使用资源 1,它必须检查互斥锁以查看它是否正在使用中,然后等到使用它的线程解锁互斥锁(在本例中为线程 1)。

此外,你所描述的 res boolean 理论上是一个信号量,如果你能保证对它的原子访问(即没有竞争条件)。有关原子访问的更多信息,请参阅此处。如果每个资源都不是唯一的,这意味着线程一次只需要 2 个,那么信号量方法可能更好。

我建议在尝试制作这个模拟器之前,对多线程进行更多研究,并查看现有的解决方案,例如 c++ 的 pthreads