竞争条件和互斥

时间:2014-09-14 20:09:01

标签: c multithreading mutex race-condition

我有两个关于线程的问题,一个是关于竞争条件而另一个是关于互斥的。 所以第一个问题: 我在维基百科页面上读过有关种族状况的文章: http://en.wikipedia.org/wiki/Race_condition

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

到目前为止,我认为线程彼此平行,但从这张图片来看,似乎我解释了计算机所做的操作是如何错误的。 从这张照片中,一次只完成1个动作,虽然线程不时被切换而另一个线程做了一些动作,但这仍然是计算机一次完成的1个动作。它真的像这样吗?没有“真正的”并行计算,只是一次以非常快的速度完成了一个动作,这给出了并行计算的错觉?

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

#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;
    }
}

现在在我的例子中,我使用bool lock作为我的同步机制,以避免指针n指向的内存空间上的2个线程之间的竞争条件。 现在我在这里做的事情显然不会起作用,因为虽然我避免了2个线程之间指针n所指向的内存空间的竞争条件,但是可能会出现一个新的竞争条件而不是bool lock变量。

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

  • A因为锁定为假而退出while循环
  • A将锁定设置为true
  • B在while循环中等待,因为lock设置为true
  • 增加n
  • 指向的值
  • A将lock设置为false
  • A到达while循环
  • A因为锁定为假而退出while循环
  • B因为锁定为假而退出while循环
  • A set lock to true
  • B将lock设置为true

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

好的,到目前为止,这是我的理解和上述问题的解决方案,我们需要一个互斥锁。 我很好,一种神奇的条件种族证明的数据类型。 我只是不明白如何使用互斥锁类型它不会发生在其他所有类型的情况下,这就是我的问题,我想了解为什么互斥量以及这是如何发生的。

1 个答案:

答案 0 :(得分:1)

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

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

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

简而言之,您必须非常小心地对两个线程同时访问变量时发生的情况做出任何假设,尤其是在C / C ++中。这种互动非常令人惊讶,我要说,为了保持安全,你应该确保代码中没有竞争条件,例如:通过始终使用互斥锁来访问线程之间共享的内存。

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

关于互斥体的事情是它们是通过对它们所使用的系统(硬件和操作系统)的大量知识来实现​​的,并且可以直接帮助或者对编译器本身有深入的了解。

C语言不允许您直接访问硬件和操作系统的所有功能,因为平台可能彼此非常不同。相反,C专注于提供一个抽象级别,允许您为许多不同的平台编译相同的代码。不同的&#34;基本&#34;数据类型只是C标准提出的一组数据类型,几乎可以在任何平台上得到支持 - 但是编译程序的实际硬件通常不仅限于那些类型和操作

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