c - 如何实现线程安全队列

标签 c multithreading pthreads queue posix

我之前在 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);
}

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

最佳答案

代码不是线程安全的。

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()ed。

现在,到目前为止讨论的是逻辑问题。

但是,需要考虑物理问题。

想象一下,我们有一个锁定机制,线程可以同步它们的行为,这样一次只有一个线程可以执行第 1 到 4 行中的代码,例如互斥量,这是一个只有一个线程一次可以“持有”的对象,并且在尝试获取互斥量时会阻塞线程,直到持有线程释放。

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 核心(不是线程)执行的写入仅对该核心上的线程立即可见;不要在其他内核上运行线程。

仅仅同步执行是不够的;同时,我们还必须确保一个核心执行的写入对其他核心可见。

(Un)幸运的是,所有现代同步方法也执行此写入刷新(例如,当您获得互斥锁时,您还会刷新所有写入内存)。我说很遗憾,因为您并不 - 总是 - 需要这种行为,而且它对性能有害。

关于c - 如何实现线程安全队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10721148/

相关文章:

c - 未在函数中传递预期值

java - 从java应用程序到elasticsearch的并发输出

java - 循环方法中的线程

c++ - 带有部分结果的 std::async/std::future 超时

c++ - 测量创建线程所需的时间

c - 将数据存储在数组中

更改函数中的字符串

c - Helgrind 在运行时停止程序

c++ - 为什么一个函数接受一个空指针?

java - 您可以使用 JNI 在从 Java 调用的 C++ 函数中创建新的 JVM 吗?