音频线程

Audio threading

本文关键字:线程 音频      更新时间:2023-10-16

我的应用程序中有一个单独的音频线程,因为这在当时听起来是个好主意,但现在我不知道其他线程将如何与音频线程通信。

audioThread() {
while(!isCloseRequested) {
If(audio.dogSoundRequested) {
audio.playDogSound();
        }
    }
}
otherThread() {
Audio.dogSoundRequested();
}

这是一种有效的音频线程方式吗?或者你看到这个设置有问题吗?

这里的关键问题似乎是

1:如何使audio.dogSoundRequestedisCloseRequested线程安全。

2:audioThread正忙于等待(例如无限旋转直到audio.dogSoundRequested变为true

正如其他人所建议的那样,您可以使用互斥来保护这两个变量,但这太过分了——此外,在音频代码中,为了避免优先级反转问题,不使用阻塞同步通常是一种很好的形式。

相反,假设您使用的是C++11或C++14,您可以使用原子变量,这些变量是轻量级的,并且(在大多数实现中)不会阻塞:

#include <atomic>
...
std::atomic<bool> dogSoundRequested{false};
std::atomic<bool> isCloseRequested{false};

对std::atomic的读取和写入与内置类型具有相同的约定,但将生成代码,以确保读取和写入相对于其他线程是原子的,并且结果与其他CPU同步。

audio.dogSoundRequested的情况下,您想要这两种效果,而在isCloseRequested的情况中,结果在其他CPU上立即可见。

要解决忙等待问题,请使用条件变量在有事情要做时唤醒audioThread

#include <condition_variable>
std::mutex m;
std::condition_variable cv;
audioThread() 
{
    while(!isCloseRequested) 
    {
        m.lock();
        cv.wait(m);
        // wait() returns with the mutex still held.
        m.unlock();
        if(audio.dogSoundRequested) 
        {
            audio.playDogSound();
        }
    }
}

void dogSoundRequested()
{
    dogSoundRequested = true;
    cv.notify_one();
}

除了使用互斥,这里还有一个用于多线程的简单设置

//  g++ -o multi_threading  -pthread -std=c++11 multi_threading.cpp
#include <iostream>
#include <thread>
#include <exception>
#include <mutex>
#include <climits>  // min max of short int
void launch_consumer() {
    std::cout << "launch_consumer" << std::endl;
}   //  launch_consumer
void launch_producer(std::string chosen_file) {
    std::cout << "launch_producer " << chosen_file << std::endl;
}   //  launch_producer
// -----------

int main(int argc, char** argv) {
    std::string chosen_file = "audio_file.wav";
    std::thread t1(launch_producer, chosen_file);
    std::this_thread::sleep_for (std::chrono::milliseconds( 100));
    std::thread t2(launch_consumer);
    // -------------------------
    t1.join();
    t2.join();
    return 0;
}

与其用互斥和条件变量使代码复杂化,不如考虑制作一个线程安全的FIFO。在这种情况下,可能有多个作者和一个消费者。应用程序的其他线程是该FIFO的写入程序,audioThread()是消费者。

// NOP = no operation
enum AudioTask {NOP, QUIT, PLAY_DOG, ...};
class Fifo
{
    public:
    bool can_push() const;   // is it full?
    void push(AudioTask t);  // safely writes to the FIFO
    AudioTask pop();         // safely reads from the FIFO, if empty NOP
};

现在,假设fifoaudio是应用程序类成员,audioThread()就更干净了:

void audioThread()
{
    bool running = true;
    while(running)
    {
        auto task = fifo.pop();
        switch(task)
        {
            case NOP: std::this_thread::yield(); break;
            case QUIT: running = false; break;
            case PLAY_DOG: audio.playDogSound(); break;
        }
    }
}

最后,调用代码只需要将任务推入FIFO:

void request_dog_sound()
{
    fifo.push(PLAY_DOG);
}
void stop_audio_thread()
{
    fifo.push(QUIT);
    audio_thread.join();
}

这将线程安全同步的细节放在Fifo类中,使应用程序的其余部分更干净。

如果要确保没有其他线程接触playDogSound()函数,请使用互斥锁来锁定资源。

std::mutex mtx;
audioThread() {
    while(!isCloseRequested) {
        if (audio.dogSoundRequested) {
            mtx.lock();
            audio.playDogSound();
            mtx.unlock();
        }
    }
}