我正在编写一个pthreaded网络应用程序(在C中)并且认为我应该创建一个线程来处理每个传入的连接。但是,我无法理解我应该使用哪种设计,因为我必须将连接数限制为固定数字(例如,5)。
在阅读pthread_join的手册页时,我发现:
There is no pthreads analog of waitpid(-1, &status, 0), that is, "join with any terminated
thread". If you believe you need this functionality, you probably need to rethink your
application design.
这是为什么?我怎么能完成我的目标? 感谢
答案 0 :(得分:1)
这是一个很好的问题。
我认为手册页作者所处的标准推理是需要使用waitpid
获取进程,否则他们会将资源留在其中。 pthreads的情况并非如此。如果您不需要知道特定线程何时终止(或者不需要其返回代码),您可以将其作为分离线程;它结束了就是这样。相反,如果你真的需要加入一个帖子,你应该知道你需要加入哪一个。
进一步思考,父进程与其子进程之间存在直接的一对多关系。 Waitpid
将等待其子女,操作系统将跟踪这些孩子并将其交给父母。无论创建了多少代进程,父都会收回其子进程,都会发生这种情况。
在线程程序中,任何线程都可以创建其他线程。在这种情况下,pthread_join
有什么意义?如果所有线程都要与这一个catchall线程连接,那么一切都很好。但是一些线程真的需要与其子线程连接的程序呢,而其余的线程可以通过catchall加入呢?操作系统或pthreads如何在不构建大量基础架构的情况下跟踪每种情况下join
适用的情况?
我想这是可能的,我想每个人都有机会希望在pthreads中有一个通用的waitpid
类比,但实际上,这可能是一个很大的开销,主要是烦恼。当你发现自己处于多个线程要加入的情况但是你不知道哪个线程会先结束时你可以构建一个队列(或者使用一个管道或其他什么)并让垂死的线程表明它应该加入
答案 1 :(得分:0)
如果你对并发线程的数量进行限制,最好的办法是经常在前面创建所有线程(工作者)并让它们在无限循环中运行,等待在队列上工作。
然后主线程负责将工作项(在这种情况下为连接)添加到该队列以供工作人员处理。
这样做的好处是简单和快捷。简单,因为你永远不必担心线程的启动或停止,除了一次,它们总是在运行,并为工作项目提供服务。
速度出于同样的原因。它就像一个固定大小的线程池。它还以最有效的方式处理工作项(工作负载自动平衡,因为线程只有在完成前一个项目时才会要求新项目。)
在伪代码中,这将类似于以下内容。主线只是在进入队列时将工作项添加到队列中。一旦完成所有内容,它就可以为每个线程发布一个特殊的完成工作项,然后等待它们全部完成。
def queue workqueue = empty
def main:
# start the threads
create threadId[5]
for each threadId (i):
threadId[i] = startThread (worker)
# main work loop, finished with (for example) external signal.
while not finished:
get workitem from some source
add workitem to workqueue
# Place one special FINISH work item for each thread.
for each threadId (i):
add FINISH item to workqueue
# Wait for all threads to exit, then exit main.
for each threadId (i):
wait for threadId[i] to exit
exit
工作线程同样简单。无限循环,根据需要获取工作项并处理它们。
如果工作项是完成工作项,则退出,保证每个线程只获得一个和一个完成工作项:
def worker:
# Simple infinite loop to get work items.
while true:
get workitem from workqueue
# Exit if told to.
if workitem is a FINISH item:
break
# Otherwise, process the item and loop around to request next.
process workitem
exit
答案 2 :(得分:0)
请允许我首先回应paxdiablo的建议,即允许线程保持持久性,而不是允许它们终止并重新启动它们。另外,我回应selbie建议使用非阻塞I / O,但我会将它与你的固定线程耦合(我认为每个CPU只有1个)。使用它们可以实现更高的工作负载并最大化机器的CPU资源。
要回答您的问题,但是,如果您希望能够以终止顺序加入线程,则需要线程与收割者通信他们的终止顺序。这可以通过pipe
相对简单地完成。当一个线程终止时,它会将其tid
写入管道,而收割者会读取tid
并执行pthread_join
。
int tid_pipe[2];
pipe(tid_pipe);
void * thread_proc (void *arg) {
/* ... */
/* thread exiting */
pthread_t me = pthread_self();
write(tid_pipe[1], &me, sizeof(me));
return 0;
}
/* thread reaper */
while (read(tid_pipe[0], &tid, sizeof(tid)) == sizeof(tid)) {
pthread_join(tid, &retval);
/* ... */
}
但是,作为替代方案,您可以允许您的线程分离运行,而不必担心加入。相反,您可以使用条件变量来允许主线程知道何时可以启动另一个线程。
void * thread_proc (void *arg) {
pthread_detach(pthread_self());
/* ... */
/* thread exiting */
pthread_mutex_lock(&m);
if (thread_count++ == 0) pthread_cond_signal(&c);
pthread_mutex_unlock(&m);
return 0;
}
/* thread spawner */
while (waiting_for_work()) {
pthread_mutex_lock(&m);
while (thread_count == 0) pthread_cond_wait(&c, &m);
pthread_mutex_unlock(&m);
/* ... handle work with new thread ... */
}
答案 3 :(得分:0)
由于线程没有像进程一样在层次结构中组织,因此等待“任何”线程意味着任何线程都可以访问有关所有线程的全局信息。在某种数据结构(或其他)中组织此类信息将是一定的开销,因为所有线程创建和终止都必须通过那里。这种开销是自愿避免的,线程意味着轻量且快速。
还有另一个方面是,有些线程甚至无法连接,即那些已经分离或从一开始就分离的线程。对他们施加这样的开销甚至是不可接受的。
答案 4 :(得分:-1)
如果它是一个多线程服务器,每个客户端有一个线程,只需计算出来并重新计算它们。在客户端线程创建时使用'clientCount'int的原子inc / dec(即在accept()线程中),并且在客户端 - 服务器线程退出之前,如果count>则不再接受()。 5,(即直接从accept()线程发出'Too many connections,try later'页面,而不是创建新的服务器 - 客户端线程)。
试着忘记'加入' - 想象一下你从未读过它。