我有多个读线程和一个写线程。如果我在其中一个读取线程上锁定互斥锁并从中发送广播,是否可以保证在pthread_cond_wait()上等待写入线程锁定互斥锁,或者是否有可能锁定pthread_mutex_lock()上的另一个读取线程将锁定互斥?主要问题是pthread_cond_wait()优先于pthread_mutex_lock()?
如果没有,我怎样才能实现互斥锁永远被pthread_cond_broadcast()上的写线程锁定?
实施例
阅读帖子:
pthread_mutex_lock(mutex);
pthread_cond_broadcast(cond);
pthread_mutex_unlock(mutex);
写线程:
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
答案 0 :(得分:0)
让我们假设读取和写入两个线程在同一时刻到达pthread_mutex_lock
。因此,要么写线程在pthread_mutex_lock
调用上获取互斥锁,要么读取线程。
如果是写线程,则读取线程将等待pthread_mutex_lock
。写入时,通过调用pthread_cond_wait
发布mutex
并阻止cond
。它以原子方式完成。因此,当读取线程是mutex
的grantex时,我们可以确定读取的线程在cond
上等待。因此,cond
上的广播到达写线程,它不再等待cond
,但仍在pthread_cond_wait
的范围内 - 尝试锁定mutex
(保持为读线程)。在广播cond
之后,读取线程释放mutex
并且它转到写入线程。因此,写入线程最终会从pthread_cond_wait
锁定mutex
退出。记得稍后解锁。
如果它是读取线程,则写入线程将在pthread_mutex_lock
上等待,读取将在cond
上广播信号,然后释放mutex
。之后,写线程获取mutex
上的pthread_mutex_lock
并立即释放pthread_cond_wait
等待cond
(请注意,之前的cond
广播无效在当前pthread_cond_wait
)。在读取线程的下一次迭代中,它获取mutex
上的锁定,在cond
上发送广播并解锁mutex
。这意味着写入线程在cond
上向前移动并获得对mutex
的锁定。
它是否回答了有关优先权的问题?
评论后更新。
假设我们有一个帖子(让我们将其命名为A
以供将来参考)持有mutex
上的锁,而其他几个尝试获取相同的锁。一旦第一个线程释放锁定,就无法预测哪个线程会获得锁定。此外,如果A
线程有一个循环并尝试重新获取mutex
上的锁,则有可能会被授予此锁并且其他线程将继续等待。添加pthread_cond_wait
并不会改变授予锁定的范围。
让我引用POSIX规范的片段(参见https://stackoverflow.com/a/9625267/2989411以供参考):
这些函数以原子方式释放互斥锁并导致调用线程阻塞条件变量cond;原子地在这里意味着原子地关于另一个线程访问互斥锁然后条件变量"。也就是说,如果另一个线程能够在即将发布的线程之后获取互斥锁,那么在该线程中对pthread_cond_broadcast()或pthread_cond_signal()的后续调用应该表现得就像是在约to-block线程已被阻止。
这只是关于操作顺序的标准给出的保证。将锁定授予其他线程的顺序是相当不可预测的,并且它会根据时间上的一些非常细微的波动而改变。
仅针对与互斥锁相关的代码,请使用以下代码进行播放:
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *th(void *arg) {
int i;
char *s = arg;
for (i = 0; i < 10; ++i) {
pthread_mutex_lock(&mutex);
printf("%s %d\n", s, i);
//sleep(1);
pthread_mutex_unlock(&mutex);
#if 0
pthread_yield();
#endif
}
return NULL;
}
int main() {
int i;
for (i = 0; i < 10; ++i) {
pthread_t t1, t2, t3;
printf("================================\n");
pthread_create(&t1, NULL, th, "t1");
pthread_create(&t2, NULL, th, " t2");
pthread_create(&t3, NULL, th, " t3");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
}
return 0;
}
在一台机器(单CPU)上,它始终显示从t3开始的整个循环,然后是t2,最后是t1。在另一个(2个内核)上,线程的顺序更随机,但在将互斥锁授予其他线程之前,几乎总是为每个线程显示整个循环。很少有这样的情况:
t1 8
t1 9
t3 0
t2 0
t2 1
[removed other t2 output]
t2 8
t2 9
t3 1
t3 2
通过将pthread_yield
替换为#if 0
来启用#if 1
并观看结果并检查输出。对我而言,它以两个线程显示其输出隔行扫描的方式工作,然后第三个线程终于有机会工作。添加另一个或多个线程。玩睡眠等。它确认了随机行为。
如果您想进行一些实验,请编译并运行以下代码。它是单一生产者 - 多个消费者模型的一个例子。它可以使用两个参数运行:第一个是消费者线程的数量,第二个是生成的数据系列的长度。如果没有给出参数,则有一个消费者线程和120个要处理的项目。我还建议在标记为/* play here */
的地方使用sleep / usleep:更改参数的值,完全删除睡眠,在适当的时候移动它到临界区或用pthread_yield替换并观察行为的变化。
#define _GNU_SOURCE
#include <assert.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
struct data_t {
int seq;
int payload;
struct data_t *next;
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct data_t *first = NULL, *last = NULL;
int in_progress = 1;
int num_data = 120;
void push(int seq, int payload) {
struct data_t *e;
e = malloc(sizeof(struct data_t));
e->seq = seq;
e->payload = payload;
e->next = NULL;
if (last == NULL) {
assert(first == NULL);
first = last = e;
} else {
last->next = e;
last = e;
}
}
struct data_t pop() {
struct data_t res = {0};
if (first == NULL) {
res.seq = -1;
} else {
res.seq = first->seq;
res.payload = first->payload;
first = first->next;
if (first == NULL) {
last = NULL;
}
}
return res;
}
void *producer(void *arg __attribute__((unused))) {
int i;
printf("producer created\n");
for (i = 0; i < num_data; ++i) {
int val;
sleep(1); /* play here */
pthread_mutex_lock(&mutex);
val = rand() / (INT_MAX / 1000);
push(i, val);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
printf("prod %3d %3d signaled\n", i, val);
}
in_progress = 0;
printf("prod end\n");
pthread_cond_broadcast(&cond);
printf("prod end signaled\n");
return NULL;
}
void *consumer(void *arg) {
char c_id[1024];
int t_id = *(int *)arg;
sprintf(c_id, "%*s c %02d", t_id % 10, "", t_id);
printf("%s created\n", c_id);
while (1) {
struct data_t item;
pthread_mutex_lock(&mutex);
item = pop();
while (item.seq == -1 && in_progress) {
printf("%s waits for data\n", c_id);
pthread_cond_wait(&cond, &mutex);
printf("%s got signal\n", c_id);
item = pop();
}
if (!in_progress && item.seq == -1) {
printf("%s detected end of data.\n", c_id);
pthread_mutex_unlock(&mutex);
break;
}
pthread_mutex_unlock(&mutex);
printf("%s processing %3d %3d\n", c_id, item.seq, item.payload);
sleep(item.payload % 10); /* play here */
printf("%s processed %3d %3d\n", c_id, item.seq, item.payload);
}
printf("%s end\n", c_id);
return NULL;
}
int main(int argc, char *argv[]) {
int num_cons = 1;
pthread_t t_prod;
pthread_t *t_cons;
int i;
int *nums;
if (argc > 1) {
num_cons = atoi(argv[1]);
if (num_cons == 0) {
num_cons = 1;
}
if (num_cons > 99) {
num_cons = 99;
}
}
if (argc > 2) {
num_data = atoi(argv[2]);
if (num_data < 10) {
num_data = 10;
}
if (num_data > 600) {
num_data = 600;
}
}
printf("Spawning %d consumer%s for %d items.\n", num_cons, num_cons == 1 ? "" : "s", num_data);
t_cons = malloc(sizeof(pthread_t) * num_cons);
nums = malloc(sizeof(int) * num_cons);
if (!t_cons || !nums) {
printf("Out of memory!\n");
exit(1);
}
srand(time(NULL));
pthread_create(&t_prod, NULL, producer, NULL);
for (i = 0; i < num_cons; ++i) {
nums[i] = i + 1;
usleep(100000); /* play here */
pthread_create(t_cons + i, NULL, consumer, nums + i);
}
pthread_join(t_prod, NULL);
for (i = 0; i < num_cons; ++i) {
pthread_join(t_cons[i], NULL);
}
free(nums);
free(t_cons);
return 0;
}
我希望我已经清除了你的疑虑并给了你一些代码来试验并对pthread行为有所信心。