我正在编写一个程序,对收到的每个数据包执行一些处理。我已经采用了一些多线程代码并将其放入自己的程序中:

#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;时编写的,但这不会太大地改变结果。

07-28 03:03
查看更多