【问题标题】:while inside while not working properly in c++虽然在里面,但在 C++ 中不能正常工作
【发布时间】:2014-06-16 15:17:10
【问题描述】:

我在 C++ 中有奇怪的情况(至少对我来说:D)

我的代码是:

static void startThread(Object* r){
    while(true)
    {
        while(!r->commands->empty())
        {
            doSomthing();
        }
    }
}

我使用 boost 将这个函数作为线程启动,其中 r 中的命令是队列...这个队列我在另一个线程中填充...

问题是,如果我先填满队列,然后开始这个步骤,一切正常......但是如果我先运行 startThread,然后填满队列命令,它就不起作用...... doSomething()不会运行...

如果我修改 startThread:

static void startThread(Object* r){
    while(true)
    {
        std::cout << "c" << std::endl;
        while(!r->commands->empty())
        {
            doSomthing();
        }
    }
}

我刚刚添加了 cout……它正在工作……谁能解释为什么它与 cout 一起工作而不是没有?或者有人知道可能出了什么问题?

也许编译器正在做某种优化?我不这么认为... :( 谢谢

【问题讨论】:

  • 如果你只发送空队列,你的线程很快就会结束。当您打印时,您的父线程有机会填充队列。使用一些同步。如果您的队列不是线程安全的,则从 2 个线程更新队列可能会导致未定义的结果。
  • 它正在与 cout 一起“工作”,因为您有一个错误,它导致的减速正在掩盖。首先,您需要使用某种同步机制来保护对队列的访问。
  • @Dusan Plavak - But if I run the startThread first 这就是问题所在。你相信当你调用启动线程的函数时,线程函数会立即执行。这是一个错误的假设。如果不使用(甚至学习)正确的同步,例如信号量、互斥锁、原子操作等,你就无法编写多线程程序。
  • @PaulMcKenzie 我没有假设哪个线程先启动,我确保 startThread 先启动...无论如何,问题就像 Konrad Rudolph 所描述的那样,并提供了链接...
  • @DusanPlavak - 你是如何确保 startThread 函数首先启动的?您的问题不清楚您是如何启动该功能的。你是直接调用startThread函数吗? startThread(); FillUpTheQueue();你做到了吗?还是您这样做了:callThreadedFunction( startThread ); fillUpTheQueue()callThreadedFunction 是一些启动线程的通用函数(不管是不是 boost)。

标签: c++ multithreading while-loop


【解决方案1】:

但是,如果我先运行 startThread,然后再填充队列命令,它就不起作用了... doSomething() 将不会运行

当然不是!你期待什么?您的队列是空的,所以!r-&gt;commands-&gt;empty() 将是false

我刚刚添加了 cout... 它正在工作

你很幸运。 cout 比较慢,所以你的主线程有机会在内部 while 测试第一次执行之前填满队列。

那么为什么线程在被主线程填充后看不到r-&gt;commands 的更新版本呢?因为你的代码中没有任何东西表明你的变量会从外部改变,所以编译器假设它不会。

事实上,编译器看到你的r的指针不能改变,所以它可以从内部循环中删除多余的检查。使用多线程代码时,您需要明确告诉 C++ 可以使用 atomic memory access 从不同的上下文更改变量。

【讨论】:

  • 我不认为是这种情况,因为对象 r 是指针,所以如果我以后感觉到命令是否为空也没关系...?
  • 外部while 轮询r-&gt;commands 列表。但编译器不承认,该对象是从“外部”改变的。您必须使用 volatile 告诉编译器。
  • @KonradRudolph 为什么会这样?我正在从第三个线程填充该队列并且它工作正常,我的意思是在主线程中队列被第三个线程填充......
  • 编译器无法删除任何循环,因为这会改变行为。如果empty 返回false,则存在内部无限循环。如果它是true,则存在一个外部无限循环,因为它缓存了第一次执行的结果,因为编译器不希望从外部更改r
  • @Dusan 基本上是的。请注意,互斥体有两件事情,都是必要的:它同步访问,它对读取和写入操作(内存屏障)进行排序,这反过来又阻止了编译器将读取操作作为优化删除。
【解决方案2】:

有两个相互关联的问题:

  • 您不会强制重新加载r-&gt;commandsr-&gt;commands-Yempty(),因此您的编译器会尽心尽力地寻找性能的巅峰,缓存了结果。如果无法证明缓存仍然有效,则添加更多代码可能会使编译器删除此优化。
  • 您有数据争用,因此您的程序有未定义的行为。 (我假设doSomething() 删除了一个元素,而其他一些线程添加了元素。

    1.10 多线程执行和数据竞争 § 21

    如果一个程序在不同线程中包含两个相互冲突的操作,则该程序的执行包含数据竞争, 至少其中一个不是原子的,也没有发生在另一个之前。任何此类数据竞争都会导致 未定义的行为。 [注:可以证明程序正确使用互斥锁和memory_order_- seq_cst 操作以防止所有数据竞争并且不使用其他同步操作,其行为就像 由它们的组成线程执行的操作只是交错的,每个值计算一个 对象取自该交错中对该对象的最后一个副作用。这通常被称为 “顺序一致性”。但是,这仅适用于无数据竞争的程序和无数据竞争的程序 无法观察到大多数不改变单线程程序语义的程序转换。在 事实上,大多数单线程程序转换仍然是允许的,因为任何运行 不同,因此必须执行未定义的操作。 ——尾注] 22

【讨论】:

    【解决方案3】:

    当你第一次运行线程然后填满队列时,不进入内部循环是合乎逻辑的,因为test !r-&gt;commands-&gt;empty() 是真的。添加cout 语句后,它正在工作,因为打印输出需要一些时间,同时另一个线程填满队列。所以条件再次变为真。但是在多线程环境中依赖这个事实并不是好的编程。

    【讨论】:

    • 但是外部 while 写成这样:while(true) 所以它会一次又一次地检查内部循环条件......
    • @DusanPlavak 然后通过缓存进行优化可能是原因。正如我所说,在多线程环境中依赖这些事实并不是好的编程。您应该提供一些同步原语。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-21
    • 1970-01-01
    • 1970-01-01
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多