【问题标题】:Race condition and mutex竞争条件和互斥锁
【发布时间】:2014-11-08 08:36:36
【问题描述】:

我有两个关于线程的问题,一个是关于竞争条件的,另一个是关于互斥锁的。 所以第一个问题: 我在 wikipedia page 中阅读了有关比赛条件的信息: http://en.wikipedia.org/wiki/Race_condition

在 2 个线程之间的竞争条件示例中,如下所示: http://i60.tinypic.com/2vrtuz4.png[

到目前为止,我相信线程彼此并行工作,但从这张图片来看,我似乎解释了计算机如何执行错误的操作。 从这张图片来看,一次只完成一个动作,虽然线程不时切换并且另一个线程开始做一些动作,但这仍然是计算机一次完成一个动作。真的是这样吗?没有“真正的”并行计算,一次只能以非常快的速度完成一项操作,这给人一种并行计算的错觉?

这引出了我关于互斥锁的第二个问题。 我读过,如果线程读/写相同的内存,我们需要某种同步机制。我读过普通的数据类型不会做,我们需要一个互斥锁。 我们以下面的代码为例:

#include <stdio.h>
#include <stdbool.h>
#include <windows.h>
#include <process.h>

bool lock = false;

void increment(void*);
void decrement(void*);

int main()
{
    int n = 5;
    HANDLE hIncrement = (HANDLE)_beginthread(increment, 0, (void*)&n);
    HANDLE hDecrement = (HANDLE)_beginthread(decrement, 0, (void*)&n);
    WaitForSingleObject(hIncrement, 1000 * 500);
    WaitForSingleObject(hDecrement, 1000 * 500);
    return 0;
}

void increment(void *p)
{
    int *n = p;
    for(int i = 0; i < 10; i++)
    {
        while (lock)
       {

       }
       lock = true;
       (*n)++;
       lock = false;
    }
}

void decrement(void *p)
{
    int *n = p;
    for(int i = 0; i < 10; i++)
    {
        while (lock)
       {

       }
       lock = true;
       (*n)--;
       lock = false;
    }
}

现在在我的示例中,我使用布尔锁作为我的同步机制,以避免 2 个线程之间在指针 n 指向的内存空间上发生争用情况。 现在我在这里所做的显然不会奏效,因为虽然我避免了两个线程之间指针 n 指向的内存空间的竞争条件,但可能会发生 bool lock 变量的新竞争条件。

让我们考虑以下事件序列(A = 递增线程,B = 递减线程):

  • 由于锁为假,A 退出了 while 循环
  • A 可以将 lock 设置为 true
  • B 在 while 循环中等待,因为 lock 设置为 true
  • 增加n所指向的值
  • A 将 lock 设置为 false
  • A 进入 while 循环
  • 由于锁为假,A 退出了 while 循环
  • B 退出 while 循环,因为锁为假
  • A 将 lock 设置为 true
  • B 将锁定设置为真

从这里我们得到 2 个非同步线程的意外行为,因为 bool 锁不是竞争条件证明

好的,到目前为止,这是我的理解和解决我们上述问题的方法,我们需要一个互斥锁。 我很好,这是一种神奇地证明条件竞争的数据类型。 我只是不明白 mutex 类型不会像其他所有类型一样发生,这就是我的问题,我想了解为什么 mutex 以及这是如何发生的。

【问题讨论】:

  • 可以在多处理器系统上同时运行多个线程。
  • 您不能使用 SO 来学习线程等新的专业知识。我建议你找到一个好的网站和教程并开始学习。您的问题表明您完全不知道线程是什么或它是如何工作的。
  • 我认为这是一个完全有效的问题,尽管围绕它有很多额外的说明,这可能是由于对不熟悉的上下文有点困惑(我在自己的问题中发现了同样的趋势)。所以,我认为@Omlis532 之后的问题是:“当所有基本类型都不是线程安全时,线程安全互斥类型如何存在?”
  • @CarloWood 我没有说我说的一定是正确的,我说这是我的理解,这就是为什么我来这里并就试图理解的主题提出问题更多的。对我说你不理解它去互联网阅读它(虽然我做过)无论如何都没有帮助。
  • @Medo42 关于我的第二个问题是正确的 :)

标签: c multithreading mutex race-condition


【解决方案1】:

关于您的第一个问题:实际上是否有几个不同的线程同时运行,或者是否只是作为快速切换实现,这取决于您的硬件。如今,典型的 PC 有多个内核(通常每个内核都有多个线程),因此您必须假设事情确实是同时发生的。

但即使你只有一个单核系统,事情也不是那么容易。这是因为通常允许编译器重新排序指令以优化代码。它也可以例如选择在 CPU 寄存器中缓存一个变量,而不是每次访问它时从内存中加载它,而且每次写入该变量时它也不必将其写回内存。只要结果与按照原始顺序运行原始代码的结果相同,编译器就可以这样做——只要没有其他人密切关注实际发生的情况,例如不同的线程。

一旦您确实拥有不同的内核,请考虑它们都有自己的 CPU 寄存器甚至缓存。即使一个内核上的线程写入某个变量,只要该内核不将其缓存写回共享内存,另一个内核就不会看到这种变化。

简而言之,当两个线程同时访问变量时,您必须非常小心地做出任何假设,尤其是在 C/C++ 中。交互可能非常令人惊讶,我想说,为了安全起见,您应该确保代码中没有竞争条件,例如通过始终使用互斥体来访问线程之间共享的内存。

我们可以巧妙地进入第二个问题:互斥体有什么特别之处,如果所有基本数据类型都不是线程安全的,它们如何工作?

关于互斥体的问题在于,它们的实现需要大量关于它们所使用的系统(硬件和操作系统)的知识,并且需要直接帮助或对编译器本身有深入的了解。

C 语言无法让您直接访问硬件和操作系统的所有功能,因为平台之间可能存在很大差异。相反,C 专注于提供一个抽象级别,允许您为许多不同的平台编译相同的代码。不同的“基本”数据类型只是 C 标准提出的一组数据类型,几乎可以在任何平台上以某种方式支持 - 但您的程序将编译的实际硬件通常不受限制到那些类型和操作。

换句话说,不是所有你可以用你的电脑做的事情都可以用 C 的整数、字节、赋值、算术运算符等来表达。例如,PC 通常使用 80 位浮点类型进行计算,这些浮点类型通常根本不直接映射到 C 浮点类型。更重要的是,还有一些 CPU 指令会影响多个 CPU 内核如何协同工作。此外,如果您了解 CPU,您通常会了解 C 标准不保证的基本类型的一些行为(例如,对 32 位整数的加载和存储是否是原子的)。有了这些额外的知识,就有可能为那个特定平台实现互斥锁,而且它通常需要例如直接用汇编语言编写,因为在普通 C 中没有必要的功能。

【讨论】:

  • 您认为您可以将我链接到使用互斥锁和线程的好教程吗?我正在使用 _beginthread 创建线程并将其类型转换为 HANDLE,所以我在网上查看过,但我找不到使用 _beginthread 类型将其转换为 HANDLE 并使用互斥锁的人,所以我认为网上发布的内容不适用到我的代码。将不胜感激!
  • 我从未使用该 API 进行多线程处理,但在 msdn.microsoft.com/de-de/library/y6h8hye8.aspx 有关于它的 MSDN 文档。在示例程序中,他们使用函数CreateMutexWaitForSingleObjectReleaseMutex。尽管这些互斥锁是跨进程的,但您应该知道,这通常比您想要/需要的要多。
  • 表示API创建的线程是不同核心的线程或者..?
  • 抱歉,请阅读一下什么是进程以及什么是线程。我的意思是,该 API 中的互斥锁可用于同步多个进程,而不仅仅是多个线程。这与 CPU 内核完全无关。
猜你喜欢
  • 2012-10-27
  • 1970-01-01
  • 2012-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多