这种情况是否被视为竞争条件?

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!