在Linux平台上的C++中,线程同步方案

4

我正在为Linux平台实现多线程的C++程序,需要一个类似于WaitForMultipleObjects()功能的函数。

在寻找解决方案时,我发现有一些文章描述了如何在Linux中实现WaitForMultipleObjects()功能,并提供了示例代码,但这些示例并不能满足我的需求。

在我的情况下,场景非常简单。我有一个守护进程,在其中,主线程向外部(例如DLL)暴露了一个方法/回调。 DLL的代码不在我的控制之下。同样,主线程创建了一个名为“Thread 1”的新线程。线程1必须执行一种无限循环,等待关闭事件(守护进程关闭)或等待通过上述公开的方法/回调信号发送的可用数据事件。

简而言之,该线程将在关闭事件和数据可用事件上等待,如果关闭事件被触发,则等待会满足且循环将被中断,但是如果数据可用事件被触发,等待也会满足,线程将处理业务逻辑。

在Windows中,这似乎是非常直接的。以下是我场景的基于MS Windows的伪代码。

//**Main thread**

//Load the DLL
LoadLibrary("some DLL")

//Create a new thread
hThread1 = __beginthreadex(..., &ThreadProc, ...)

//callback in main thread (mentioned in above description) which would be called by the     DLL
void Callbackfunc(data)
{
    qdata.push(data);
    SetEvent(s_hDataAvailableEvent);
}

void OnShutdown()
{
    SetEvent(g_hShutdownEvent);
    WaitforSingleObject(hThread1,..., INFINITE);
    //Cleanup here
}

//**Thread 1**

unsigned int WINAPI ThreadProc(void *pObject)
{
    while (true)
    {

        HANDLE hEvents[2];
        hEvents[0] = g_hShutdownEvent;
        hEvents[1] = s_hDataAvailableEvent;

        //3rd parameter is set to FALSE that means the wait should satisfy if state of  any one of the objects is signaled.
        dwEvent = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
        switch (dwEvent) 
        {   
            case WAIT_OBJECT_0 + 0: 
            // Shutdown event is set, break the loop
            return 0;   

            case WAIT_OBJECT_0 + 1:
            //do business processing here
            break; 

            default: 
            // error handling
        }
    }
}

我希望能在Linux上实现相同的功能。据我所知,当涉及到Linux时,它有完全不同的机制,需要我们注册信号。如果终止信号到达,进程将会知道它即将关闭,但在此之前,进程必须等待运行中的线程优雅地关闭。


POSIX等效于WFMO是select/poll/epoll。在Linux中,几乎所有的东西都是可等待的文件描述符,您可以始终创建一些管道作为可等待的线程间事件。因此,您的信号处理程序只需写入连接到另一个线程的管道,该线程将被唤醒。您甚至可以使用数据报套接字或数据报FIFO作为线程安全队列。 - Ben Voigt
2个回答

4
在Linux中正确的方法是使用条件变量。虽然这与Windows中的WaitForMultipleObjects不同,但您将获得相同的功能。
使用两个bool来确定是否有可用数据或必须进行关闭操作。然后让关闭函数和数据函数都相应地设置布尔值,并发出条件变量信号。
#include <pthread.h>

pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_t hThread1; // this isn't a good name for it in linux, you'd be 
                    // better with something line "tid1" but for 
                    // comparison's sake, I've kept this

bool shutdown_signalled;
bool data_available;

void OnShutdown()
{
    //...shutdown behavior...
    pthread_mutex_lock(&mutex);
    shutdown_signalled = true;
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cv);
}

void Callbackfunc(...)
{
    // ... whatever needs to be done ...
    pthread_mutex_lock(&mutex);
    data_available = true;
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cv);
}


void *ThreadProc(void *args)
{
    while(true){
        pthread_mutex_lock(&mutex);
        while (!(shutdown_signalled || data_available)){
            // wait as long as there is no data available and a shutdown
            // has not beeen signalled
            pthread_cond_wait(&cv, &mutex);
        }
        if (data_available){
            //process data
            data_available = false;
        }
        if (shutdown_signalled){
            //do the shutdown
            pthread_mutex_unlock(&mutex);
            return NULL;
        }
        pthread_mutex_unlock(&mutex); //you might be able to put the unlock
        // before the ifs, idk the particulars of your code
    }
}
int main(void)
{
    shutdown_signalled = false;
    data_available = false;
    pthread_create(&hThread1, &ThreadProc, ...);
    pthread_join(hThread1, NULL);
    //...
}

我知道Windows也有条件变量,所以这不应该看起来太陌生。我不知道Windows对它们有什么规定,但在POSIX平台上,wait需要放在while循环内部,因为可能会发生“虚假唤醒”。


1
如果您想编写Unix或Linux特定的代码,可以使用不同的API:
  • pthread:提供线程、互斥锁、条件变量
  • IPC(进程间通信)机制:互斥锁、信号量、共享内存
  • 信号
对于线程,第一个库是必需的(在Linux上有更低级别的系统调用,但更繁琐)。对于事件,这三个库都可以使用。
系统关闭事件会生成终止(SIG_TERM)和杀死(SIG_KILL)信号广播到所有相关进程。因此,也可以通过这种方式启动单个守护程序的关闭。游戏的目标是捕获信号并启动进程关闭。重要的点是:
  • 信号机制被设计成不需要等待

    只需使用sigaction安装一个处理程序,系统会自动完成其余部分。

  • 信号被设置到进程中,任何线程都可以拦截它(处理程序可以在任何上下文中执行)

    因此,您需要安装一个信号处理程序(请参见sigaction(2)),并以某种方式将信息传递给其他线程,告知应用程序必须终止。

最方便的方法可能是有一个全局互斥锁保护的标志,所有线程都会定期查询该标志。信号处理程序将设置该标志以指示关闭。对于工作线程,这意味着

  • 告诉远程主机服务器正在关闭,
  • 在读取时关闭其套接字
  • 处理所有剩余的接收到的命令/数据并发送答案
  • 关闭套接字
  • 退出

对于主线程,这意味着启动对工作线程的加入,然后退出。

这个模型不应该干扰数据正常处理的方式:如果捕获到信号,对于阻塞调用selectpoll会返回错误EINTR,对于非阻塞调用,线程会定期检查标志位,因此也能正常工作。


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