这种情况是否被视为竞争条件?
Is this situation considered as race condition?
考虑以下代码:
#define _XOPEN_SOURCE 600
#define _DEFAULT_SOURCE
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define ENTRY_NUM 100
struct value {
pthread_mutex_t mutex;
int i;
};
struct entry {
atomic_uintptr_t val;
};
struct entry entries[ENTRY_NUM];
void* thread1(void *arg)
{
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = (struct value*) atomic_load(&entries[i].val);
if (val == NULL)
continue;
pthread_mutex_lock(&val->mutex);
printf("%d\n", val->i);
pthread_mutex_unlock(&val->mutex);
}
return NULL;
}
void* thread2(void *arg)
{
/*
* Do some costy operations before continuing.
*/
usleep(1);
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = (struct value*) atomic_load(&entries[i].val);
pthread_mutex_lock(&val->mutex);
atomic_store(&entries[i].val, (uintptr_t) NULL);
pthread_mutex_unlock(&val->mutex);
pthread_mutex_destroy(&val->mutex);
free(val);
}
return NULL;
}
int main() {
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = malloc(sizeof(struct value));
pthread_mutex_init(&val->mutex, NULL);
val->i = i;
atomic_store(&entries[i].val, (uintptr_t) val);
}
pthread_t ids[2];
pthread_create(&ids[0], NULL, thread1, NULL);
pthread_create(&ids[1], NULL, thread2, NULL);
pthread_join(ids[0], NULL);
pthread_join(ids[1], NULL);
return 0;
}
假设在函数 thread1 中,entries[i].val 被加载,然后调度程序安排进程休眠。
然后thread2从usleep中醒来,因为((struct val*) entries[0].val)->mutex没有被锁定,thread2锁定它,将NULL存储到entries[0].val并释放原来的条目数 [0].val.
现在,这是竞争条件吗?如果是这样,如何在不锁定条目或条目[0]的情况下避免这种情况?
你是对的,我的朋友,在这样的代码中确实存在竞争条件。
让我总体来说,根据定义,线程是竞争条件的虾,这对于任何其他库实现的线程也是正确的,这些线程在编译时间之前未被您的编译器确认。
关于您的具体示例,是的,正如您自己解释的那样,因为我们无法假设您的调度程序何时开始运行,线程 1 可以原子加载您的条目,上下文切换到线程 2,然后线程 1 之前释放这些条目再次获得处理器时间。您如何防止或避免此类竞争条件?避免在不锁定它们的情况下访问它们,即使原子加载是 "atomic read" 你在逻辑上允许其他线程访问这些条目。 thread1 和 thread2 的整个代码范围都应该用互斥锁保护。尽管使用 atomic_load
,您只是保证在那个原子时间,不会对该条目进行其他访问,但是在 atomic_load
和您第一次调用 pthread_mutex_lock
上下文之间的时间开关确实会发生!正如您自己提到的那样,这既是不好的做法,也是逻辑上错误的。 - 因此正如我已经说过的,您应该使用 pthread_mutex_lock
保护整个范围
一般来说,正如我在本文开头所说的那样,考虑到您的编译器在编译期间不知道线程的概念,它对您甚至可能没有意识到的竞争条件非常敏感, - 例如:当访问某些共享内存的不同区域时,编译器不会考虑其他线程可能存在并根据需要访问某些内存,并且可能会影响内存的不同区域,即使逻辑上代码本身不会, 并且代码的 "correctness" 是有效的。
有一些论文发表在这个叫做
Threads Cannot be Implemented as a Library by Hans-J Boehm 我强烈建议你阅读它,我保证它会增加你对竞争条件和一般线程使用pthread!
考虑以下代码:
#define _XOPEN_SOURCE 600
#define _DEFAULT_SOURCE
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define ENTRY_NUM 100
struct value {
pthread_mutex_t mutex;
int i;
};
struct entry {
atomic_uintptr_t val;
};
struct entry entries[ENTRY_NUM];
void* thread1(void *arg)
{
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = (struct value*) atomic_load(&entries[i].val);
if (val == NULL)
continue;
pthread_mutex_lock(&val->mutex);
printf("%d\n", val->i);
pthread_mutex_unlock(&val->mutex);
}
return NULL;
}
void* thread2(void *arg)
{
/*
* Do some costy operations before continuing.
*/
usleep(1);
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = (struct value*) atomic_load(&entries[i].val);
pthread_mutex_lock(&val->mutex);
atomic_store(&entries[i].val, (uintptr_t) NULL);
pthread_mutex_unlock(&val->mutex);
pthread_mutex_destroy(&val->mutex);
free(val);
}
return NULL;
}
int main() {
for (int i = 0; i != ENTRY_NUM; ++i) {
struct value *val = malloc(sizeof(struct value));
pthread_mutex_init(&val->mutex, NULL);
val->i = i;
atomic_store(&entries[i].val, (uintptr_t) val);
}
pthread_t ids[2];
pthread_create(&ids[0], NULL, thread1, NULL);
pthread_create(&ids[1], NULL, thread2, NULL);
pthread_join(ids[0], NULL);
pthread_join(ids[1], NULL);
return 0;
}
假设在函数 thread1 中,entries[i].val 被加载,然后调度程序安排进程休眠。
然后thread2从usleep中醒来,因为((struct val*) entries[0].val)->mutex没有被锁定,thread2锁定它,将NULL存储到entries[0].val并释放原来的条目数 [0].val.
现在,这是竞争条件吗?如果是这样,如何在不锁定条目或条目[0]的情况下避免这种情况?
你是对的,我的朋友,在这样的代码中确实存在竞争条件。
让我总体来说,根据定义,线程是竞争条件的虾,这对于任何其他库实现的线程也是正确的,这些线程在编译时间之前未被您的编译器确认。
关于您的具体示例,是的,正如您自己解释的那样,因为我们无法假设您的调度程序何时开始运行,线程 1 可以原子加载您的条目,上下文切换到线程 2,然后线程 1 之前释放这些条目再次获得处理器时间。您如何防止或避免此类竞争条件?避免在不锁定它们的情况下访问它们,即使原子加载是 "atomic read" 你在逻辑上允许其他线程访问这些条目。 thread1 和 thread2 的整个代码范围都应该用互斥锁保护。尽管使用 atomic_load
,您只是保证在那个原子时间,不会对该条目进行其他访问,但是在 atomic_load
和您第一次调用 pthread_mutex_lock
上下文之间的时间开关确实会发生!正如您自己提到的那样,这既是不好的做法,也是逻辑上错误的。 - 因此正如我已经说过的,您应该使用 pthread_mutex_lock
一般来说,正如我在本文开头所说的那样,考虑到您的编译器在编译期间不知道线程的概念,它对您甚至可能没有意识到的竞争条件非常敏感, - 例如:当访问某些共享内存的不同区域时,编译器不会考虑其他线程可能存在并根据需要访问某些内存,并且可能会影响内存的不同区域,即使逻辑上代码本身不会, 并且代码的 "correctness" 是有效的。 有一些论文发表在这个叫做 Threads Cannot be Implemented as a Library by Hans-J Boehm 我强烈建议你阅读它,我保证它会增加你对竞争条件和一般线程使用pthread!