c++ - 同时等待条件 (pthread_cond_wait) 和套接字更改 (select)

标签 c++ multithreading thread-safety pthreads asyncsocket

我正在用 c/c++ 编写一个 POSIX 兼容的多线程服务器,它必须能够异步接受、读取和写入大量连接。服务器有几个工作线程,它们执行任务并偶尔(并且不可预测地)排队将数据写入套接字。客户端偶尔也会(并且不可预测地)将数据写入套接字,因此服务器也必须异步读取。一种明显的方法是为每个连接提供一个线程,该线程从其套接字读取和写入;不过,这很丑陋,因为每个连接都可能持续很长时间,因此服务器可能不得不持有成百上千个线程来跟踪连接。

更好的方法是让一个线程使用 select()/pselect() 函数处理所有通信。即,单个线程等待任何套接字可读,然后生成一个作业来处理输入,只要输入可用,该作业将由其他线程池处理。每当其他工作线程为连接生成输出时,它就会排队,并且通信线程在写入之前等待该套接字可写。

问题在于,当服务器的工作线程对输出进行排队时,通信线程可能正在 select() 或 pselect() 函数中等待。有可能,如果几秒钟或几分钟内没有输入到达,排队的输出 block 将只等待通信线程完成 select()ing。然而,这不应该发生——数据应该尽快写入。

现在我看到了几个线程安全的解决方案。一种是让通信线程忙等待输入,并每隔十分之一秒左右更新一次等待写入的套接字列表。这不是最佳的,因为它涉及忙等待,但它会起作用。另一种选择是使用 pselect() 并在新输出排队时发送 USR1 信号(或类似的东西),允许通信线程立即更新它正在等待可写状态的套接字列表。我在这里更喜欢后者,但仍然不喜欢将信号用于应该是条件的东西 (pthread_cond_t)。另一种选择是在 select() 正在等待的文件描述符列表中包含一个虚拟文件,每当需要将套接字添加到 select() 的可写 fd_set 时,我们就将一个字节写入该虚拟文件;这将唤醒通信服务器,因为该特定的虚拟文件随后将是可读的,从而允许通信线程立即更新它的可写 fd_set。

我的直觉是,第二种方法(带有信号)是对服务器进行编程的“最正确”方法,但我很好奇是否有人知道以上哪种方法最有效,一般来说,是否以上任何一项都会导致我不知道的竞争条件,或者如果有人知道这个问题的更通用的解决方案。我真正想要的是一个 pthread_cond_wait_and_select() 函数,它允许通信线程等待套接字的变化或来自条件的信号。

提前致谢。

最佳答案

这是一个相当普遍的问题。

一个常用的解决方案是使用管道作为从工作线程返回到 I/O 线程的通信机制。完成任务后,工作线程将指向结果的指针写入管道。 I/O 线程与其他套接字和文件描述符一起在管道的读取端等待,一旦管道准备好读取它就会唤醒,检索指向结果的指针并继续将结果推送到客户端连接中-阻塞模式。

请注意,由于小于或等于 PIPE_BUF 的管道读取和写入是原子的,因此可以一次性写入和读取指针。由于原子性保证,甚至可以让多个工作线程将指针写入同一个管道。

关于c++ - 同时等待条件 (pthread_cond_wait) 和套接字更改 (select),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8593004/

相关文章:

java - 'synchronized function' 和 'synchronized block inside the function' 之间有什么功能上的区别吗?

c++ - 使用 int 计算并输出 float?

c++ - 可调用结果类型的推导

c++ - 增长序列的长度 C++

java - 多线程循环的效率

Java Hibernate 多线程

python - 当程序在循环中时让 Tkinter 按钮做某事

c++ - 可以为 3rd 方库取代/取代默认 C++ std::allocator 吗?

java - Java 中 synchronized()/wait()/notifyAll() 有什么作用?

java - 这个类线程安全吗?如果不能,有人可以解释一个场景吗?