我们可以在单线程程序中有竞争条件吗?

时间:2014-01-30 17:27:44

标签: multithreading concurrency race-condition

你可以在here找到关于什么是竞争条件的非常好的解释。

最近我看到很多人对竞争条件和线程做出令人困惑的陈述。

我了解到竞争条件只能在线程之间发生。但我看到的代码看起来像竞争条件,在事件和基于异步的语言中,即使程序是单线程,如Node.js,GTK +等。

我们可以在一个线程程序中遇到竞争条件吗?

3 个答案:

答案 0 :(得分:28)

所有示例都使用非常接近Javascript的虚构语言。

简短:

  1. 竞争条件只能在 两个或多个线程之间发生。我们不能在单个线程进程中具有竞争条件(例如,在单个线程中,非I / O执行程序)。

  2. 但在许多情况下,单个线程程序可以:

    1. 给出与竞争条件类似的情况,例如基于事件的程序和事件循环,但不是真正的竞争条件

    2. 触发其他线程之间或与其他线程的竞争条件,例如:

      1. 其他程序,如客户
      2. 库线程或服务器
  3. I)竞争条件只能在两个或多个线程之间发生

    只有当两个或多个线程尝试访问共享资源而不知道其他线程的未知指令同时修改共享资源时,才会出现竞争条件。这会产生未确定的结果。 (这非常重要。)

    单线程进程只不过是一系列已知指令,因此即使指令的执行顺序不易读取,也会产生确定的结果在代码中。

    II)但我们不安全

    II.1)与种族条件相似的情况

    许多编程语言通过事件信号实现异步编程功能,由主循环事件循环处理>检查事件队列并触发侦听器。这方面的例子是Javascript,libuevent,reactPHP,GNOME GLib ......有时,我们可以找到似乎是竞争条件的情况,但它们不是

    调用事件循环的方式总是已知,因此结果是确定,即使指令的执行顺序不易读取(甚至不能如果我们不知道图书馆,请阅读。

    示例:

    setTimeout(
      function() { console.log("EVENT LOOP CALLED"); },
      1
    ); // We want to print EVENT LOOP CALLED after 1 milliseconds
    
    var now = new Date();
    while(new Date() - now < 10) //We do something during 10 milliseconds
    
    console.log("EVENT LOOP NOT CALLED");
    

    在Javascript输出中始终(您可以在node.js中测试):

    EVENT LOOP NOT CALLED
    EVENT LOOP CALLED
    

    因为,当堆栈为空(所有函数都已返回)时,将调用事件循环。

    请注意,这只是一个示例,在以不同方式实现事件的语言中,结果可能会有所不同,但仍然会由实现决定。

    II.2)其他线程之间的竞争条件,例如:

    II.2.i)与客户等其他程序

    如果其他进程正在请求我们的进程,我们的程序不会以原子方式处理请求,并且我们的进程在请求之间共享一些资源,则客户端之间可能存在竞争条件

    示例:

    var step;
    on('requestOpen')(
      function() {
        step = 0;
      }
    );
    
    on('requestData')(
      function() {
        step = step + 1;
      }
    );
    
    on('requestEnd')(
      function() {
        step = step +1; //step should be 2 after that
        sendResponse(step);
      }
    );
    

    在这里,我们有一个经典的竞赛条件设置。如果请求在另一个结束之前打开,step将重置为0.如果由于两个并发请求而在requestData之前触发了两个requestEnd事件,则步骤将达到3. em>但这是因为我们将事件序列视为未确定。我们期望程序的结果大部分时间未确定,输入未确定。

    事实上,如果我们的程序是单线程,给定一系列事件,结果仍然总是被确定。客户之间的竞争条件

    有两种方法可以理解这件事:

    • 我们可以将客户视为我们计划的一部分(为什么不呢?),在这种情况下,我们的程序是多线程的。故事的结尾。
    • 更常见的是,我们可以认为客户不属于我们的计划。在这种情况下,它们只是输入。当我们考虑一个程序是否有确定的结果时,我们使用输入来做到这一点。否则,即使是最简单的程序return input;也会有不确定的结果。

    请注意:

    • 如果我们的流程以原子方式处理请求,则与客户端之间存在 mutex 相同,并且没有竞争条件。
    • 如果我们可以识别请求并将变量附加到请求的每一步都相同的请求对象,则客户端之间没有共享资源且没有竞争条件

    II.2.ii)使用库线程

    在我们的程序中,我们经常使用产生其他进程或线程的库,或者只使用其他进程执行I / O(并且I / O总是未确定)。

    示例:

    databaseClient.sendRequest('add Me to the database');
    
    databaseClient.sendRequest('remove Me from the database');
    

    这可以触发异步库中的竞争条件。如果sendRequest()在将请求发送到数据库之后但在请求真正执行之前返回,则会出现这种情况。我们立即发送另一个请求,我们无法知道在评估第二个请求之前是否会执行第一个请求,因为数据库在另一个线程上工作。程序和数据库进程之间存在竞争条件

    但是,如果数据库与程序在同一个线程上(在现实生活中并不经常发生),则在处理请求之前sendRequest将无法返回。 (除非请求已排队,但在这种情况下,结果仍然确定,因为我们确切知道如何以及何时读取队列。)

    结论

    简而言之,单线程程序并非免于竞争条件。但它们只能与外部程序的其他线程一起发生 。我们的计划结果可能未确定,因为我们的计划从其他计划收到的输入未确定。

答案 1 :(得分:3)

种族条件可以在任何具有同时执行的过程的系统中发生,这些过程会在外部过程中造成状态变化,例如:

  • 多线程,
  • 事件循环,
  • 多重处理
  • 指令级并行性,其中指令的无序执行必须注意避免出现竞争状况,
  • 电路设计,
  • 约会(浪漫)
  • 真实比赛,例如奥运会。

答案 2 :(得分:0)

是。

“竞争条件”是指程序的结果可以根据运行的顺序(线程,异步任务,单个指令等)改变的情况。

例如,使用Javascript:

setTimeout(() => console.log("Hello"), 10);
setTimeout(() => setTimeout(() => console.log("World"), 4), 4);

// VM812:1 Hello
// VM812:2 World

setTimeout(() => console.log("Hello"), 10);
setTimeout(() => setTimeout(() => console.log("World"), 4), 4);

// VM815:2 World
// VM815:1 Hello

因此,显然,此代码取决于JS事件循环的工作方式,如何订购/选择任务,在执行过程中发生了哪些其他事件,甚至取决于操作系统选择如何安排JS运行时进程。

这是人为设计的,但是实际程序可能会出现“ Hello”需要在“ World”之前运行的情况,这可能会导致一些令人讨厌的不确定性错误。我不确定人们如何认为这不是“真正的”比赛条件。

数据竞赛

不可能在单线程代码中进行数据竞争。

“数据竞赛”是多个线程以不固定的方式同时访问共享资源,或者专门针对内存:多个线程访问相同的内存,其中一个(或更多)正在写。当然,只有一个线程是不可能的。

这似乎就是@jillro的答案。


注意:“竞赛条件”和“数据竞赛”的确切定义尚未达成共识。但是,如果它看起来像一个竞赛条件,表现得像一个竞赛条件,并且引起了像竞赛条件一样令人讨厌的不确定性错误,那么我认为它应该被称为竞赛条件。