我正在编写一个程序,对收到的每个数据包执行一些处理。我已经采用了一些多线程代码并将其放入自己的程序中:
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
struct pktstruct {
//u_char *args; - Not used
const struct pcap_pkthdr *head;
const u_char *packt;
int thread_no;
};
void* packet_handler(void *arguments);
void handler_dispatcher(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
pthread_t threads[20];
volatile sig_atomic_t thread_done[20];
int main(int argc, char *argv[])
{
for (int i = 0; i < 20; i ++){
thread_done[i] = 0;
}
while (1) {
handler_dispatcher(NULL,NULL,NULL);
}
}
void* packet_handler(void *arguments)
{
struct pktstruct *args = arguments;
printf("Hello %d\n", args->thread_no);
thread_done[args->thread_no] = 1;
return NULL;
}
void handler_dispatcher(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){
struct pktstruct pkt;
pkt.head = header;
pkt.packt = packet;
for (int t = 0; t < 20; t++) {
if ( thread_done[t] ) {
//printf("Thread %d is done! Joining...\n", t);
pthread_join(threads[t], NULL);
thread_done[t] = 0;
}
}
for (int q = 0; q < 20; q++) {
if ( !thread_done[q] ) {
pkt.thread_no = q;
pthread_create(&threads[q], NULL, &packet_handler, &pkt);
break;
}
}
}
最初,我只是为每个数据包生成一个线程。我对C不太熟悉,所以花了我一段时间才意识到诸如线程无法清除之类的东西,返回NULL比pthread_exit(NULL)等更好。
该程序将打印一堆“ Hello”,然后停止正常运行。 HTOP对于VIRT显示256G,对于RES显示266M,但是它们停止增长的唯一原因是该程序停止工作。
我知道使用互斥锁是“好的设计”,但是我认为,由于我正在每次迭代中检查空闲插槽,因此即使我错过了一个可用的线程,也可以肯定地知道下一次迭代。
老实说,我也尝试过使用互斥锁-在handler_dispatcher中的for循环之前和之后,以及packet_handler中的赋值之前和之后进行锁定和解锁,但是在使用256G VIRT和266M RES。
我做错了什么事?
编辑:添加标题。用gcc test.c -o test -Wall -lpthread编译
最佳答案
您永远不会使用volatile
变量在线程之间进行通信。 volatile
不表示atomic
。从多个线程访问非原子变量是未定义的。但是,让我们假装这不是问题,请看一下您的两个循环。
第一循环:
for (int t = 0; t < 20; t++) {
if ( thread_done[t] ) {
//printf("Thread %d is done! Joining...\n", t);
pthread_join(threads[t], NULL);
thread_done[t] = 0;
}
}
第一次调用
handler_dispatcher
时,还没有线程,并且所有thread_done
成员均为1。您在无效线程上调用pthread_join
20次。这是不确定的,但是假设您已经以某种方式幸免于难。您将所有thread_done
设置为0。第二循环:
for (int q = 0; q < 20; q++) {
if ( !thread_done[q] ) {
pkt.thread_no = q;
pthread_create(&threads[q], NULL, &packet_handler, &pkt);
break;
}
}
首次调用该函数时,此循环创建一个线程。
当第二次调用该函数时,该线程可能仍在运行,因此第一个循环不执行任何操作。 (
printf
是执行IO的慢函数,因此可以安全地假设它比main while(1)
thread_done ).
pthread_t`对象中is zero, so you create a new thread. The new thread ID overwrites the original
循环的单次迭代花费的时间多1000倍。永远,所以原来的线程是不可能加入的。当第三次调用该函数时,您创建的第二个线程可能仍在运行。因此,您创建了一个新线程。新的线程ID会覆盖原始的
pthread_t
对象,该对象将永远丢失,因此无法加入原始线程。第四次调用该函数时,您创建的第三个线程可能仍在运行。因此,您创建了一个新线程。新的线程ID会覆盖原始的
pthread_t
对象,该对象将永远丢失,因此无法加入原始线程。当第5380次调用该函数时,您创建的第一个线程最终终止。它设置其
thread_done
标志,并且您的第一个循环执行join
。只有它不加入第一个线程,而是加入第5379个线程。该调用将阻塞,直到该线程也完成为止。然后序列重新开始。总而言之,我认为这一系列事件可能并不是您想要的。传递指向局部变量的指针只是此派的头等大事。我建议得到一本正确的食谱。
这是在
thread_done[i] = 0;
为thread_done[i] = 1;
时编写的,但这不会太大地改变结果。