如何实现线程安全的队列

9

我之前在Python中使用过多线程库,但这是我首次尝试在C语言中进行线程处理。我想创建一个工作线程池,这些工作线程应该从队列中推入或弹出数据。以下代码还不够完整,但是这是我目前所做的:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMTHREADS 20 /* number of threads to create */

typedef struct node node;
typedef struct queue queue;

struct node {
    char *name;
    node *next;
};

struct queue {
    node *head;
    node *tail;
};

/* pop: remove and return first name from a queue */
char *pop(queue *q)
{
    if (q->head == NULL)
        return NULL;
    char *name = q->head->name;
    node *tmp = q->head;
    q->head = q->head->next;
    free(tmp);
    return name;
}

/* push: add name to the end of the queue */
int push(queue *q, char *name)
{
    node *new = malloc(sizeof(node));
    if (new == NULL)
        return -1;
    new->name = name;
    new->next = NULL;
    if (q->tail != NULL)
        q->tail->next = new;

    q->tail = new;
    if (q->head == NULL) /* first value */
        q->head = new;
    return 0;
}

/* printname: get a name from the queue, and print it. */
void *printname(void *sharedQ)
{
    queue *q = (queue *) sharedQ;
    char *name = pop(q);
    if (name == NULL)
        pthread_exit(NULL);
    printf("%s\n",name);
    pthread_exit(NULL);
}

int main()
{
    size_t i;
    int rc;
    pthread_t threads[NUMTHREADS];
    char *names[] = {
        "yasar",
        "arabaci",
        "osman",
        "ahmet",
        "mehmet",
        "zeliha"
    };

    queue *q = malloc(sizeof(queue));
    q->head = NULL;
    q->tail = NULL;

    /* number of elements in the array */
    size_t numelems = sizeof(names) / sizeof(char *);

    for (i = 0; i < numelems; i++) /* push each name */
        push(q, names[i]);

    for (i = 0; i < NUMTHREADS; i++) { /* fire up threads */
        rc = pthread_create(&threads[i], NULL, printname,
                (void *)q);
        if (rc) {
            printf("Error, return code from pthread is %d\n", rc);
            exit(-1);
        }
    }

    pthread_exit(NULL);
}

我尝试了上面的代码,它总是只打印每个名称一次。它没有跳过任何名称,也没有重复打印相同的名称。另一方面,我不确定这个队列实现是否线程安全。所以我的问题是,这是一个线程安全的队列吗?如果不是,为什么?如何使其线程安全?


结构体不需要typedef,它们已经有了自己的类型。 - user82238
2个回答

8

该代码不是线程安全的。

push和pop函数不是线程安全的。在代码中,只有单个线程执行push操作,因此没有问题,但是多个线程执行pop操作。

1. char *name = q->head->name;
2. node *tmp = q->head;
3. q->head = q->head->next;
4. free(tmp);

假设线程A执行到第2行,然后线程B执行到第4行。线程A恢复执行,发现q->head已经被free()释放。
目前为止,这讨论的是逻辑问题。但是,还有物理问题需要考虑。
假设我们有一种锁定机制,使得线程可以同步它们的行为,这样只有一个线程可以同时执行1到4行的代码,例如互斥锁(mutex),它是一个只有一个线程可以“持有”的对象,尝试获取互斥锁会阻塞线程,直到持有线程释放。
0. get mutex
1. char *name = q->head->name;
2. node *tmp = q->head;
3. q->head = q->head->next;
4. free(tmp);
5. release mutex

我们仍然有一个问题,即由任何一个CPU核心(而不是线程)执行的写操作仅对该核心上的线程立即可见,而对其他核心上的线程不可见。
仅同步执行是不够的;同时,我们还必须确保核心执行的写操作对其他核心也可见。
幸运或者不幸的是,所有现代同步方法都执行这种写入刷新(例如,当您获取互斥锁时,也会将所有写入刷新到内存中)。我说不幸是因为您并不总是需要这种行为,而且它会损害性能。

3

由于多个线程可能同时修改链表中的指针,从而潜在地破坏它,因此它不是线程安全的。

这里有一个非常类似的问题的答案: C语言中的多写线程安全队列

在那里,您可以看到如何使队列线程安全。


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