简体   繁体   English

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

[英]C++ Lock a mutex as if from another thread?

I'm writing an Audio class that holds an std::thread for refilling some buffers asynchronously.我正在编写一个Audio类,它包含一个std::thread用于异步重新填充一些缓冲区。 Say we call the main thread A and the background (class member) thread B. I'm using an std::mutex to block thread B whenever the sound is not playing, that way it doesn't run in the background when unnecessary and doesn't use excess CPU power.假设我们调用主线程 A 和后台(类成员)线程 B。我使用std::mutex在没有播放声音时阻塞线程 B,这样它就不会在不必要的时候在后台运行不使用多余的 CPU 功率。 The mutex locked by thread A by default, so thread B is blocked, then when it's time to play the sound thread A unlocks the mutex and thread B runs (by locking then immediately unlocking it) in a loop.默认情况下,线程 A 锁定了互斥锁,因此线程 B 被阻塞,然后当需要播放声音时,线程 A 解锁互斥锁,线程 B 循环运行(通过锁定然后立即解锁)。

The issue comes up when thread B sees that it's reached the end of the file.当线程 B 看到它已到达文件末尾时,问题就出现了。 It can stop playback and clean up buffers and such, but it can't stop its own loop because thread B can't lock the mutex from thread A.它可以停止播放和清理缓冲区等,但它不能停止自己的循环,因为线程 B 无法锁定线程 A 的互斥锁。

Here's the relevant code outline:这是相关的代码大纲:

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

_ _

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
}

In the above setup, when the background thread sees that it's reached EOF, it would call the class's Stop() function, which supposedly locks the mutex to stop the background thread.在上面的设置中,当后台线程看到它到达 EOF 时,它会调用类的Stop()函数,该函数应该锁定互斥锁以停止后台线程。 This doesn't work because the mutex would have to be locked by the main thread, not the background thread (in this example, it crashes in ThreadFunc because the background thread attempts a lock in its main loop after already locking in Stop() ).这不起作用,因为互斥锁必须由主线程而不是后台线程锁定(在本例中,它在ThreadFunc崩溃,因为后台线程在已经锁定Stop()后尝试在其主循环中锁定) .

At this point the only thing I could think of would be to somehow have the background thread lock the mutex as if it was the main thread, giving the main thread ownership of the mutex... if that's even possible?在这一点上,我唯一能想到的是以某种方式让后台线程锁定互斥锁,就好像它是主线程一样,赋予主线程对互斥锁的所有权......如果这可能吗? Is there a way for a thread to transfer ownership of a mutex to another thread?有没有办法让线程将互斥锁的所有权转移到另一个线程? Or is this a design flaw in the setup I've created?或者这是我创建的设置中的设计缺陷? (If the latter, are there any rational workarounds?) Everything else in the class so far works just as designed. (如果是后者,是否有任何合理的解决方法?)到目前为止,该类中的其他所有内容都按设计工作。

I'm not going to even pretend to understand how your code is trying to do what it is doing.我什至不会假装理解你的代码是如何尝试做它正在做的事情。 There is one thing, however, that is evident.然而,有一件事是显而易见的。 You're trying to use a mutex for conveying some predicate state change, which is the wrong vehicle to drive on that freeway.您正在尝试使用互斥锁来传达一些谓词状态更改,这是在高速公路上行驶的错误车辆。

Predicate state change is handled by coupling three things:谓词状态更改通过耦合三件事来处理:

  • Some predicate datum一些谓词数据
  • A mutex to protect the predicate保护谓词的互斥锁
  • A condition variable to convey possible change in predicate state.一个条件变量,用于传达谓词状态的可能变化。

The Goal目标

The goal in the below example is to demonstrate how a mutex, a condition variable, and predicate data are used in concert when controlling program flow across multiple threads.下面示例中的目标是演示如何在控制跨多个线程的程序流时协同使用互斥锁、条件变量和谓词数据。 It shows examples of using both wait and wait_for condition variable functionality, as well as one way to run a member function as a thread proc.它显示了使用waitwait_for条件变量功能的示例,以及将成员函数作为线程 proc 运行的一种方法。


Following is a simple Player class toggles between four possible states:以下是在四种可能状态之间切换的简单Player类:

  • Stopped : The player is not playing, nor paused, nor quitting.已停止:播放器未播放、暂停或退出。
  • Playing : The player is playing Playing : 播放器正在播放
  • Paused : The player is paused, and will continue from whence it left off once it resumes Playing. Paused : 播放器暂停,一旦恢复播放,将从它停止的地方继续播放。
  • Quit : The player should stop what it is doing and terminate. Quit :玩家应该停止正在做的事情并终止。

The predicate data is fairly obvious.谓词数据相当明显。 the state member.议员。 It must be protected, which means it cannot be changed nor checked unless under the protection of the mutex.它必须受到保护,这意味着它不能被更改或检查,除非在互斥锁的保护下。 I've added to this a counter that simply increments during the course of maintaining the Playing state for some period of time.我添加了一个counter ,它在保持播放状态一段时间的过程中简单地递增。 more specifically:进一步来说:

  • While Playing, each 200ms the counter increments, then dumps some data to the console.播放时, counter每 200 毫秒递增一次,然后将一些数据转储到控制台。
  • While Paused, counter is not changed, but retains its last value while Playing.暂停时, counter不会改变,但在播放时保留其最后一个值。 This means when resumed it will continue from where it left off.这意味着当恢复时,它将从它停止的地方继续。
  • When Stopped, the counter is reset to zero and a newline is injected into the console output.当停止时, counter被重置为零,并且一个换行符被注入到控制台输出中。 This means switching back to Playing will start the counter sequence all over again.这意味着切换回播放将重新启动计数器序列。
  • Setting the Quit state has no effect on counter , it will be going away along with everything else.设置 Quit 状态对counter没有影响,它将与其他所有内容一起消失。

The Code编码

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

Output输出

I can't really demonstrate this.我真的无法证明这一点。 You'll have to run it and see how it works, and I invite you to toy with the states in main() as I have above.您必须运行它并查看它是如何工作的,我邀请您像上面一样玩弄main()的状态。 Do note, however, that once quit is invoked none of the other stated will be monitored.但是,请注意,一旦调用quit ,将不会监视其他任何声明。 Setting the Quit state will shut down the monitor thread.设置退出状态将关闭监视器线程。 For what its worth, a run of the above should look something like this:对于它的价值,上面的运行应该是这样的:

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.

with the first set of numbers dumped in two groups (1..15, then 16..30), as a result of playing, then pausing, then playing again.第一组数字被分成两组(1..15,然后是 16..30),作为播放的结果,然后暂停,然后再次播放。 Then a stop is issued, followed by another play for a period of ~3 seconds.然后发出停止信号,接着是约 3 秒的另一次播放。 After that, the object self-destructs, and in doing so, sets the Quit state, and waits for the monitor to terminate.之后,对象自毁,并在此过程中设置退出状态,并等待监视器终止。

Summary概括

Hopefully you get something out of this.希望你能从中有所收获。 If you find yourself trying to manage predicate state by manually latching and releasing mutexes, changes are you need a condition-variable design patter to facilitate detecting those changes.如果您发现自己试图通过手动锁定和释放互斥锁来管理谓词状态,那么您需要一个条件变量设计模式来促进检测这些变化。

Hope you get something out of it.希望你能从中有所收获。

class CtLockCS
{
public:
    //--------------------------------------------------------------------------
    CtLockCS()      { ::InitializeCriticalSection(&m_cs); }
    //--------------------------------------------------------------------------
    ~CtLockCS()     { ::DeleteCriticalSection(&m_cs); }
    //--------------------------------------------------------------------------
    bool TryLock()  { return ::TryEnterCriticalSection(&m_cs) == TRUE; }
    //--------------------------------------------------------------------------
    void Lock()     { ::EnterCriticalSection(&m_cs); }
    //--------------------------------------------------------------------------
    void Unlock()   { ::LeaveCriticalSection(&m_cs); }
    //--------------------------------------------------------------------------
protected:
    CRITICAL_SECTION m_cs;
};


///////////////////////////////////////////////////////////////////////////////
// class CtLockMX - using mutex

class CtLockMX
{
public:
    //--------------------------------------------------------------------------
    CtLockMX(const TCHAR* nameMutex = 0)
        { m_mx = ::CreateMutex(0, FALSE, nameMutex); }
    //--------------------------------------------------------------------------
    ~CtLockMX()
        { if (m_mx) { ::CloseHandle(m_mx); m_mx = NULL; } }
    //--------------------------------------------------------------------------
    bool TryLock()
        {   return m_mx ? (::WaitForSingleObject(m_mx, 0) == WAIT_OBJECT_0) : false; }
    //--------------------------------------------------------------------------
    void Lock()
        {   if (m_mx)   { ::WaitForSingleObject(m_mx, INFINITE); }  }
    //--------------------------------------------------------------------------
    void Unlock()
        {   if (m_mx)   { ::ReleaseMutex(m_mx); }   }
    //--------------------------------------------------------------------------
protected:
    HANDLE  m_mx;
};


///////////////////////////////////////////////////////////////////////////////
// class CtLockSM - using semaphore

class CtLockSM
{
public:
    //--------------------------------------------------------------------------
    CtLockSM(int maxcnt)    { m_sm = ::CreateSemaphore(0, maxcnt, maxcnt, 0); }
    //--------------------------------------------------------------------------
    ~CtLockSM()             { ::CloseHandle(m_sm); }
    //--------------------------------------------------------------------------
    bool TryLock()          { return m_sm ? (::WaitForSingleObject(m_sm, 0) == WAIT_OBJECT_0) : false;  }
    //--------------------------------------------------------------------------
    void Lock()             { if (m_sm) { ::WaitForSingleObject(m_sm, INFINITE); }  }
    //--------------------------------------------------------------------------
    void Unlock()
    {
        if (m_sm){
            LONG prevcnt = 0;
            ::ReleaseSemaphore(m_sm, 1, &prevcnt);
        }
    }
    //--------------------------------------------------------------------------
protected:
    HANDLE  m_sm;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM