C++如同从另一个线程锁定互斥锁?

The*_*802 1 c++ multithreading mutex

我正在编写一个Audio类,std::thread用于异步地重新填充一些缓冲区.假设我们调用主线程A和后台(类成员)线程B.我正在使用一个std::mutex阻止线程B,只要声音没有播放,那样它就不会在后台运行时不必要而且不使用CPU功率过剩.默认情况下,线程A锁定互斥锁,因此线程B被阻塞,然后当播放声音线程A时,解锁互斥锁并且线程B在循环中运行(通过锁定然后立即解锁).

当线程B看到它到达文件末尾时,问题出现了.它可以停止播放和清理缓冲区等,但它不能停止自己的循环,因为线程B无法从线程A锁定互斥锁.

这是相关的代码大纲:

class Audio {
private:

    // ...

    std::thread Thread;
    std::mutex PauseMutex;    // mutex that blocks Thread, locked in constructor
    void ThreadFunc();     // assigned to Thread in constructor

 public:

    // ...

    void Play();
    void Stop();
}
Run Code Online (Sandbox Code Playgroud)

_

void Audio::ThreadFunc() {

    // ... (include initial check of mutex here)

    while (!this->EndThread) {    // Thread-safe flag, only set when Audio is destructed

            // ... Check and refill buffers as necessary, etc ...

        if (EOF)
             Stop();

        // Attempt a lock, blocks thread if sound/music is not playing
        this->PauseMutex.lock();
        this->PauseMutex.unlock();
    }
}

void Audio::Play() {
     // ...
     PauseMutex.unlock();     // unlock mutex so loop in ThreadFunc can start
}

void Audio::Stop() {
     // ...
     PauseMutex.lock();     // locks mutex to stop loop in ThreadFunc
     // ^^ This is the issue here
}
Run Code Online (Sandbox Code Playgroud)

在上面的设置中,当后台线程看到它已达到EOF时,它将调用类的Stop()函数,该函数应该锁定互斥锁以停止后台线程.这不起作用,因为互斥锁必须被主线程锁定,而不是后台线程(在此示例中,它崩溃,ThreadFunc因为后台线程在已经锁定后尝试在其主循环中锁定Stop()).

在这一点上,我唯一能想到的就是以某种方式让后台线程锁定互斥锁,好像它是主线程一样,给予互斥锁的主线程所有权......如果可能的话?有没有办法让线程将互斥锁的所有权转移到另一个线程?或者这是我创建的设置中的设计缺陷?(如果是后者,是否有任何合理的解决方法?)到目前为止,班上的其他所有内容都与设计一样.

Who*_*aig 6

我甚至不会假装理解你的代码是如何尝试做它正在做的事情的.然而,有一件事是显而易见的.您正在尝试使用互斥锁来传达一些谓词状态变化,这是在高速公路上行驶的错误工具.

通过耦合三件事来处理谓词状态变化:

  • 一些谓词数据
  • 用于保护谓词的互斥锁
  • 一个条件变量,用于传达谓词状态的可能变化.

目标

以下示例中的目标是演示在跨多个线程控制程序流时如何协同使用互斥锁,条件变量和谓词数据.它显示了使用both waitwait_forcondition变量功能的示例,以及将成员函数作为线程proc运行的一种方法.


以下是一个简单的Player类在四种可能的状态之间切换:

  • 停止:玩家没有玩,也没有停顿,也没有退出.
  • 比赛:球员正在比赛
  • 暂停:播放器暂停,并且一旦恢复播放,将继续播放.
  • 退出:玩家应停止正在进行的操作并终止.

谓词数据相当明显.的状态成员.它必须受到保护,这意味着除非在互斥锁的保护下,否则不能对其进行更改或检查.我添加了这个counter,只是在维持播放状态一段时间内增加.进一步来说:

  • 在播放时,每counter增加200ms ,然后将一些数据转储到控制台.
  • 暂停时,counter不会更改,但会在播放时保留其最后一个值.这意味着恢复后它将从中断的地方继续.
  • 停止时,将counter重置为零,并将新行注入控制台输出.这意味着切换回播放将重新开始计数器序列.
  • 设置退出状态没有任何影响counter,它将与其他一切一起消失.

代码

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <unistd.h>

using namespace std::chrono_literals;

struct Player
{
private:
    std::mutex mtx;
    std::condition_variable cv;
    std::thread thr;

    enum State
    {
        Stopped,
        Paused,
        Playing,
        Quit
    };

    State state;
    int counter;

    void signal_state(State st)
    {
        std::unique_lock<std::mutex> lock(mtx);
        if (st != state)
        {
            state = st;
            cv.notify_one();
        }
    }

    // main player monitor
    void monitor()
    {
        std::unique_lock<std::mutex> lock(mtx);
        bool bQuit = false;

        while (!bQuit)
        {
            switch (state)
            {
                case Playing:
                    std::cout << ++counter << '.';
                    cv.wait_for(lock, 200ms, [this](){ return state != Playing; });
                    break;

                case Stopped:
                    cv.wait(lock, [this]() { return state != Stopped; });
                    std::cout << '\n';
                    counter = 0;
                    break;

                case Paused:
                    cv.wait(lock, [this]() { return state != Paused; });
                    break;

                case Quit:
                    bQuit = true;
                    break;
            }
        }
    }

public:
    Player()
        : state(Stopped)
        , counter(0)
    {
        thr = std::thread(std::bind(&Player::monitor, this));
    }

    ~Player()
    {
        quit();
        thr.join();
    }

    void stop() { signal_state(Stopped); }
    void play() { signal_state(Playing); }
    void pause() { signal_state(Paused); }
    void quit() { signal_state(Quit); }
};

int main()
{
    Player player;
    player.play();
    sleep(3);
    player.pause();
    sleep(3);
    player.play();
    sleep(3);
    player.stop();
    sleep(3);
    player.play();
    sleep(3);
}
Run Code Online (Sandbox Code Playgroud)

产量

我无法证明这一点.你必须运行它并看看它是如何工作的,我邀请你main()像我上面那样玩各州.但是请注意,一旦quit被调用,将不会监视其他任何声明.设置Quit状态将关闭监视器线程.对于它的价值,上面的一段应该看起来像这样:

1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.
Run Code Online (Sandbox Code Playgroud)

将第一组数字倾倒在两组(1..15,然后是16 ...... 30),由于播放,然后暂停,然后再次播放.然后发出一个停止,接着是另一个游戏,持续约3秒.之后,对象自毁,并在此过程中设置Quit状态,并等待监视器终止.

摘要

希望你从中得到一些东西.如果您发现自己试图通过手动锁定和释放互斥锁来管理谓词状态,那么您需要更改条件变量设计模式以便于检测这些更改.

希望你得到一些东西.