【问题标题】:Is this situation considered as race condition?这种情况是否被视为竞争条件?
【发布时间】:2020-08-25 22:03:27
【问题描述】:

考虑以下代码:

#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中,entry[i].val被加载,那么调度器调度进程休眠。

然后thread2从usleep中醒来,因为((struct val*) entries[0].val)->mutex没有被锁定,thread2将其锁定,将NULL存储到entry[0].val并释放原始条目[0].val.

现在,这是一个竞争条件吗?如果是这样,如何在不锁定条目或条目[0]的情况下避免这种情况?

【问题讨论】:

    标签: c multithreading locking atomic race-condition


    【解决方案1】:

    你说得对,我的朋友,这样的代码中确实存在竞争条件。

    让我从整体上说,线程根据定义是对竞争条件的对虾,这对于任何其他库实现的线程也是正确的,但编译器在编译之前未确认。

    关于您的具体示例,是的,正如您自己解释的那样,因为我们无法假设您的调度程序何时开始运行,线程 1 可以原子加载您的条目,上下文切换到线程 2,然后在线程 1 获得处理器之前释放这些条目再一次。你如何防止或避免这种竞争条件?避免在不锁定它们的情况下访问它们,即使原子加载是“原子读取”,您在逻辑上也允许其他线程访问这些条目。 thread1 和 thread2 的整个代码范围都应使用互斥锁进行保护。尽管使用了atomic_load,但您只是保证在那个原子时间不会对该条目进行其他访问,但是在atomic_load 和您第一次调用pthread_mutex_lock 之间的时间确实会发生上下文切换!正如您自己提到的那样,这既是不好的做法,在逻辑上也是错误的。 - 因此,正如我已经说过的,您应该使用 pthread_mutex_lock 保护整个范围

    一般来说,正如我在本文开头所述,考虑到您的编译器在编译期间不知道线程的概念,它对您甚至可能不知道的竞争条件非常敏感,例如:当访问某些共享内存的不同区域时,编译器不会考虑其他线程可能存在并根据需要访问某些内存,并且可能会影响内存的不同区域,即使逻辑上代码本身没有,并且代码的“正确性”是有效的。 有一些论文发表在这称为 Threads Cannot be Implemented as a Library 作者 Hans-J Boehm 我强烈建议您阅读它,我保证它将增加您对一般使用 pthread 的竞争条件和线程的理解!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-10
      • 2020-12-15
      相关资源
      最近更新 更多