我以前在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);
}
我尝试了上面的代码,它总是打印每个名称一次。它没有跳过任何名字,也没有两次打印同名。另一方面,我不确定这个队列实现的线程安全性如何。所以我的问题是,这是一个线程安全的队列吗?如果没有,为什么不呢?以及如何使其线程安全?
答案 0 :(得分:7)
代码不是线程安全的。
推送和弹出功能不是线程安全的。在代码中,push只是由一个线程执行,所以没关系,但pop正在由多个线程执行。
1. char *name = q->head->name;
2. node *tmp = q->head;
3. q->head = q->head->next;
4. free(tmp);
想象一下线程A执行到第2行并包括第2行。然后线程B执行到第4行并包括第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)幸运的是,所有现代同步方法也执行此写入刷新(例如,当您获得互斥锁时,还会刷新所有写入内存)。不幸的是,我说,因为你没有 - 总是 - 需要这种行为而且它对性能有害。
答案 1 :(得分:3)
它不是线程安全的,因为多个线程可能同时修改链表中的指针,可能会破坏它。
在这里,您可以找到一个非常相似的问题的答案: Multiple-writer thread-safe queue in C
在那里你可以看到如何使队列成为线程安全的。