C ++是否像从另一个线程一样锁定互斥锁?

时间:2018-08-03 08:35:22

标签: c++ multithreading mutex

我正在写一个ControlTemplate类,其中包含一个Audio用于异步填充一些缓冲区。假设我们调用主线程A和后台(类成员)线程B。每当声音不播放时,我就使用std::thread来阻塞线程B,这样在不必要时它就不会在后台运行并且不使用多余的CPU能力。互斥锁默认情况下由线程A锁定,因此线程B被阻塞,然后在播放声音时,线程A解锁了互斥锁,线程B循环运行(通过锁定然后立即将其解锁)。

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

这是相关的代码概述:

std::mutex

_

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();
}

在上述设置中,当后台线程看到已到达EOF时,它将调用该类的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 } 函数,该函数应该锁定互斥体以停止后台线程。这是行不通的,因为互斥锁必须由主线程而不是后台线程锁定(在此示例中,它在Stop()中崩溃,因为后台线程已经在锁定后尝试在其主循环中进行锁定) ThreadFunc

在这一点上,我唯一想到的就是以某种方式让后台线程锁定互斥锁,好像它是主线程,从而赋予互斥锁主线程所有权...如果可能的话?线程是否可以将互斥量的所有权转移到另一个线程?还是这是我创建的设置中的设计缺陷? (如果是后者,有合理的解决方法吗?)到目前为止,该类中的所有其他内容都按设计工作。

2 个答案:

答案 0 :(得分:6)

我什至不假装理解您的代码如何尝试执行其操作。然而,有一件事是显而易见的。您正在尝试使用互斥体来传达某些谓词状态变化,这是在该高速公路上行驶的错误车辆。

谓词状态更改是通过结合三件事来处理的:

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

目标

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


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

  • 已停止:玩家没有在玩,也没有暂停,也没有退出。
  • 正在播放:播放器正在播放
  • 已暂停:播放器已暂停,一旦恢复播放,将从中断处继续播放。
  • 退出:播放器应停止正在执行的操作并终止。

谓词数据非常明显。 state 成员。必须对其进行保护,这意味着除非受到互斥锁的保护,否则不能不检查对其进行更改。我添加了一个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);
}

输出

我无法真正证明这一点。您必须运行它并查看其工作方式,我邀请您像上面一样使用main()中的状态进行模拟。但是请注意,一旦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.

将第一组数字分为两组(分别是1.1.5和16..30)进行播放,然后暂停,然后再次播放。然后发出一声停止,随后进行另外3秒钟的播放。此后,对象将自毁,并以此设置退出状态,并等待监视器终止。

摘要

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

希望您能从中学到一些东西。

答案 1 :(得分:0)

=SUM(MAXIFS(B3:B14,A3:A14,{1,2,3,4}))