在C/C++共享内存中的等待和通知

35

如何在使用pthread库时,在C/C++中实现类似于Java中的wait和notify用于两个或多个线程之间的共享内存?

6个回答

39

与使用Java对象进行等待/通知不同,您需要两个对象:互斥锁和条件变量。它们使用pthread_mutex_initpthread_cond_init进行初始化。

在Java对象上同步的地方,使用pthread_mutex_lockpthread_mutex_unlock(请注意,在C中,您必须手动配对这些)。如果您不需要等待/通知,仅需锁定/解锁,则只需要互斥锁,而不需要条件变量。请记住,互斥锁不一定是“可递归”的。这意味着,如果您已经拥有锁,则无法再次获取锁,除非设置初始标志以指定您想要该行为。

在调用java.lang.Object.wait的位置,调用pthread_cond_waitpthread_cond_timedwait

在调用java.lang.Object.notify的位置,调用pthread_cond_signal

在调用java.lang.Object.notifyAll的位置,调用pthread_cond_broadcast

与Java一样,从等待函数中可能会出现虚假唤醒,因此您需要设置某些条件,在调用信号之前设置并在调用等待后检查,并且您需要在循环中调用pthread_cond_wait。与Java一样,当您等待时,互斥锁将被释放。

与Java不同,在Java中,除非您持有监视器,否则无法调用notify,但实际上,您确实可以在不持有互斥锁的情况下调用pthread_cond_signal。通常不会获得任何好处,而且通常是一个非常糟糕的想法(因为通常您想要锁定-设置条件-信号-解锁)。因此,最好将其忽略并将其视为Java。

实际上没有太多需要注意的事项,基本模式与Java相同,并非巧合。但请阅读所有这些函数的文档,因为有各种标志和有趣的行为,您需要了解和/或避免。

在C++中,你可以比仅使用pthread API更好地应用RAII于互斥锁的加锁/解锁操作。但是,取决于你能够使用哪些C++库,你可能最好使用更符合C++风格的pthread函数的包装器。

11

在您的标题中,您将C和C++混合在一起,随意使用“C/C++”。我希望您不是编写了一个混合两者的程序。

如果您使用的是C++11,则会发现可移植的pthread替代方案(在POSIX系统上,它通常在幕后使用pthread)。

您可以使用std::condition_variable+ std::mutex 进行等待/通知。 这个例子显示了如何做:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool mainReady = false;
bool workerReader = false;
 
void worker_thread()
{
    // Wait until main() sends data
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return mainReady;});
    }
 
    std::cout << "Worker thread is processing data: " << data << std::endl;
    data += " after processing";
 
    // Send data back to main()
    {
        std::lock_guard<std::mutex> lk(m);
        workerReady = true;
        std::cout << "Worker thread signals data processing completed\n";
    }
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        mainReady = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return workerReady;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 

    // wait until worker dies finishes execution
    worker.join();
}

“我希望你不要写一个混合两者的程序”,混合两种编程语言会有什么问题呢? - Michel Feinstein
在实践中,你经常混用它们。但是,当你有疑问时,比如“哦...我应该使用原始指针还是智能指针?”,你已经在使用C++(因为C没有智能指针),所以除非有一些API或其他限制禁止使用智能指针,或者它们明显是不必要的等等,否则你肯定想使用智能指针。如果你不能自动做出这个决定,你会分散注意力,试图做出太多不必要的决定,浪费时间和认知资源,而这些资源可以用来解决更难的问题。 - Domi

6

pthread_cond_wait和pthread_cond_signal可以基于某个条件进行同步。


4

使用条件变量是一种方法:在Linux下使用pthread库时可以使用它们(请参见链接)。

条件变量是类型为pthread_cond_t的变量,与适当的等待函数和稍后的进程继续使用。


3
如果您不关心可移植性,Linux提供了eventfd,它可以为您提供所需的功能。每个eventfd都有一个内部计数器。在默认模式下,如果计数器为零,则从eventfd读取将会阻塞,否则会立即返回。写入它将添加到内部计数器中。
因此,wait调用只需要一个uint64_t buf_a; read(event_fd, &buf_a, sizeof(buf_a));,其中buf必须是一个8字节的缓冲区。要通知等待的线程,您可以执行uint64_t buf_b = 1; write(event_fd, &buf_b, sizeof(buf_b));

0
如果可用,您可以使用POSIX信号量。pthread库具有互斥锁,这也可能适合您的需求。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接