为什么空的 while 循环与包含某些内容的 while 循环的反应不同?

Why an empty while loop don't react the same as a while loop with something in it?

本文关键字:while 循环 包含某 为什么      更新时间:2023-10-16

我目前有 2 个 pthreads 正在运行,我想等待其中一个线程结束,以便我的程序继续。

在我的线程中,我有一个变量,它可以是真或假(它是一个全局变量(。创建线程后(一个要求输入 cin 和一个等待 10 秒,如果达到 10 秒,它会杀死"cin"线程并结束自己,如果检测到 cin ","cin"线程会杀死"计时器"线程(我希望我的程序等待。当每个线程结束时,他们将变量"stoptimer"设置为true。

首先在创建线程后,我开始编写这样的while循环:

while(stoptimer==false){}

线程启动,我们进入 while 循环,但即使线程结束并且"停止计时器"变为 true,我们也不会退出循环。

我目前正在这样做:

rc = pthread_create(&threads[1], NULL, Timer, (void *)&td[1]);
if (rc) {
    cout << "Error:unable to create thread," << rc << endl;
    exit(-1);
}
rc = pthread_create(&threads[2], NULL, Choix, (void *)&i);
if (rc) {
    cout << "Error:unable to create thread," << rc << endl;
    exit(-1);
}
while (stoptimer==false) {
    cout<<"wait"<<endl;
}

在这里,您可以看到我正在创建线程,然后进入包含某些内容的 while 循环。如果我保持这个循环原样,它正在做我想做的事情,当计时器结束或用户输入值时,我们会退出 while,因为"stoptimer"不再为假。但我不希望这个库特在这里。

我尝试在循环中放置一个注释,以便它不为空,但它仍然反应为空。在我的理解中,如果它适用于计数,那么现在它应该可以工作,但什么都不做。

为什么要这样做? 空而循环有什么特别之处吗?

这里的问题是您在没有任何形式的线程同步的情况下读取stoptimer变量,我怀疑您在没有任何线程同步的情况下写入它。

为了保证一个线程所做的更改在另一个线程上可见,您必须使用某种形式的线程同步,或者依赖于了解底层 CPU 的内存模型(弱或强(,这通常看起来很容易,但在最好的时候可能很难做到正确。

查看您的代码,我怀疑使用条件变量跨线程同步会更明智。

问题不在于 while 循环。问题是您有一个未定义行为的竞争条件。具体来说,不允许同时从不同的线程读取和写入变量(R/R 可以,R/W 和 W/W 不能(。编译器可以通过在寄存器中缓存stoptimer的值并从寄存器而不是变量读取来自由"优化"while循环。因此,它不会确定值已更改。允许编译器执行此操作,因为在 while-循环中读取时stoptimer更改是非法的。

要解决此问题,您需要在读取和写入共享变量之前锁定互斥锁。所以你会做这样的事情:

while(true){
    pthread_mutex_lock(stoptimer_mutex);
    auto ts = stoptimer;
    pthread_mutex_unlock(stoptimer_mutex);
    if (ts==false)
        break;
}

现在这真的很长而且很乏味,您真的不想每次访问stoptimer时都这样做。要摆脱所有手动锁定,您可以使用隐式锁定和解锁的atomic<bool> stoptimer,异常安全且更高效。但这仍然不好,因为你有一个忙碌的等待。现在您(希望(放弃了 pthreads,我们可以使用标准库函数进行线程创建。

std::promise one_thread_done;
auto one_thread_future = one_thread_done.get_future();
std::thread timer_thread{Timer, td[1]}; //create threads
std::thread choix_thread{Choix, i};
one_thread_future.wait(); //wait until one of them is done

在每个线程函数中,你one_thread_done.set_value();发出信号,表明它已经在 try/catch 中完成,因为如果它已经被另一个线程设置,它会抛出一个future_error

future的优点是它们易于使用和理解。缺点是它们仅使用 1 次,没有重置future s。如果您需要反复等待其中一个线程,您将需要一个条件变量。