【问题标题】:strange proplem using two Threads and Boolean使用两个线程和布尔值的奇怪问题
【发布时间】:2014-07-10 19:05:48
【问题描述】:

(我讨厌写这样的标题。但我找不到更好的东西)

我有两个有两个线程的类。第一个检测两帧之间的运动:

void Detector::run(){

isActive = true;  

// will run forever
while (isActive){

        //code to detect motion for every frame
        //.........................................

        if(isThereMotion)
        {
            if(number_of_sequence>0){

                theRecorder.setRecording(true);                    
                theRecorder.setup();

               // cout << " motion was detected" << endl;

            }
            number_of_sequence++;
        }
        else 
        {
            number_of_sequence = 0;
            theRecorder.setRecording(false);
            // cout << " there was no motion" << endl;
            cvWaitKey (DELAY);

        }
    }
}

第二个将在开始时录制视频:

void Recorder::setup(){

    if (!hasStarted){
        this->start();
       }
}

void Recorder::run(){    

     theVideoWriter.open(filename, CV_FOURCC('X','V','I','D'), 20, Size(1980,1080), true);


if (recording){

    while(recording){
    //++++++++++++++++++++++++++++++++++++++++++++++++
    cout <<  recording << endl;
    hasStarted=true;
    webcamRecorder.read(matRecorder); // read a new frame from video
    theVideoWriter.write(matRecorder); //writer the frame into the file

    }

   }
else{
    hasStarted=false;
    cout << "no recording??" << endl;
    changeFilemamePlusOne();
    }
  hasStarted=false;
  cout << "finished recording" << endl;
  theVideoWriter.release();

}

布尔记录被函数改变:

void Recorder::setRecording(bool x){
    recording = x;
}

目标是在检测到运动后开始录制,同时防止程序开始录制两次。

真正奇怪的问题,老实说在我的脑海中没有任何意义,代码只有在我计算布尔记录(标有“++++++”)时才能工作。 Else 记录永远不会更改为 false,并且 else 语句中的代码永远不会被调用。

有没有人知道为什么会发生这种情况。我还只是从 c++ 开始,但这个问题对我来说似乎很奇怪..

【问题讨论】:

  • 你尝试std::atomic&lt;bool&gt; 输入isActiverecording 变量了吗?
  • 这绝对是一个竞争条件。 cout 引入了足够的延迟来更改执行顺序。
  • 在有人提出volatile 之前:不!
  • @isADon 因为如果你不将变量声明为atomic 编译器可以假设没有其他线程会更改字段的值并将其缓存(这是一个很好且简单的解释和细节上完全错误,但这是一个好的开始)。
  • @isADon 您可以只使用QMutex 或QT 提供的任何其他同步机制。 (不幸的是,这会将每次读取都变成共享数据的写入——互斥体本身——这会稍微影响性能,但通常没关系。)

标签: c++ multithreading boolean


【解决方案1】:

我想你的变量isThereMotionrecordingbool 类型的简单类成员。

默认情况下,对这些成员的并发访问不是线程安全的,您将面临竞争条件和各种奇怪的行为。

我建议像这样声明这些成员变量(只要您可以使用最新标准):

class Detector {
    // ...
    std::atomic<bool> isThereMotion;
};

class Recorder {
    // ...
    std::atomic<bool> hasStarted;
};

等等

幕后的原因是,即使是读取/写入一个简单的布尔值,也会拆分成多个应用于 CPU 的汇编指令,而这些指令可能会在中间被调度,以改变进程的线程执行路径。使用std::atomic&lt;&gt; 提供了类似于临界区的东西,用于自动对该变量进行读/写操作。


简而言之:将旨在从不同线程同时访问的所有内容设为原子值,或使用适当的同步机制,如std::mutex

如果您不能使用最新的 c++ 标准,您也许可以使用boost::thread 来解决您的代码可移植性问题。


注意:
从您的 cmets 来看,您的问题似乎特定于 Qt 框架,您可以使用多种机制进行同步,例如提到的QMutex


为什么volatile 在多线程环境中没有帮助?

volatile 阻止编译器仅通过假设以前以顺序方式设置的值来优化实际读取访问。它不会阻止线程在实际检索或写入值时被中断。

volatile 应该用于读取可以独立于顺序或线程执行模型更改的地址(例如总线寻址的外围硬件寄存器,其中硬件会主动更改值,例如 FPGA 在寄存器中报告当前数据吞吐量接口)。

在此处查看有关此误解的更多详细信息:
Why is volatile not considered useful in multithreaded C or C++ programming?

【讨论】:

  • 这几乎完美无缺。必须在我的 QMake 中添加一些标志,因为 QT 不允许我只使用 但之后布尔值会正确更新。非常感谢!
  • Dekker's algorithm 可以与 volatile 一起使用以同步线程,但每个线程的等待循环将受 cpu 限制,除非可以在等待循环中使用 Sleep() 之类的东西。
【解决方案2】:

您可以使用带有指向帧缓冲区的指针的节点池作为链表 fifo 消息传递系统的一部分,使用互斥锁和信号量来协调线程。要记录的每一帧的消息将被发送到记录线程(附加到它的列表并释放一个信号量),否则该节点将返回(附加)回主线程的列表。

使用基于 Windows 的同步复制文件的示例代码。主线程读入缓冲区,衍生线程从它接收的缓冲区中写入。设置代码比较长,但是实际的消息传递函数和两个线程函数简单而小。

mtcopy.zip

【讨论】:

    【解决方案3】:

    可能是活性问题。编译器可能会重新排序指令或将 isActive 提升出循环。尝试将其标记为volatile

    来自 MSDN 文档:

    声明为 volatile 的对象不会在某些优化中使用,因为它们的值可以随时更改。系统总是在请求时读取 volatile 对象的当前值,即使先前的指令要求来自同一对象的值。此外,对象的值在赋值时立即写入。

    简单示例:

    #include <iostream>
    using namespace std;
    int main() {
        bool active = true;
        while(active) {
          cout << "Still active" << endl;
        }
    }
    

    组装它: g++ -S test.cpp -o test1.a

    将 volatile 添加到 active,如 volatile bool active = true

    再次组装g++ -S test.cpp -o test2.a,看看区别diff test1.a test2.a

    <   testb   $1, -5(%rbp)
    ---
    >   movb    -5(%rbp), %al
    >   testb   $1, %al
    

    请注意,第一个在测试之前甚至不需要读取active 的值,因为循环体从不修改它。第二个版本可以。

    【讨论】:

    • 我说了什么?你看过我的评论吗?在这里使用volatile 是明显错误的建议!
    • 再解释一下:您提到的文档实际上并没有错。问题出在其他地方。 volatile 阻止编译器仅通过假设以前以顺序方式设置的值来优化实际读取访问。它不会阻止线程在实际检索或写入值时被中断。这需要通过我在回答中提到的线程同步机制来完成。
    • 除了说“不”之外,要详细说明吗?
    • volatile 对于多线程来说是NOT,原因与优化无关。 bool 的值可以在movb 执行时间和testb 执行时间之间变化。
    • 感谢您的澄清。我并不是说这是最好或最优雅的解决方案,我只是想帮助他理解活性问题,因为我认为这是问题的根源
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    相关资源
    最近更新 更多