【问题标题】:C++ mutex doesn't work - synchronization failsC++ 互斥锁不起作用 - 同步失败
【发布时间】:2021-01-27 16:02:38
【问题描述】:

我想应用尽可能简单的互斥锁。

#include <iostream>
#include <thread>
#include <vector>
#include <functional>
#include <algorithm>
#include <mutex>
using namespace std;

int sum;
static mutex m;

void addValue(int value)
{
    m.lock();
    sum += value;
    m.unlock();
}

int main()
{

    int counter1 = 0;
    int counter2 = 0;
    for (int i = 0; i < 100; i++)
    {
        thread t1(addValue, 100);
        thread t2(addValue, 200);

        if (sum == 300)
        {
            counter1++;
        }
        else
        {
            counter2++;
        }
        sum = 0;
        t1.join();
        t2.join();
    }
    cout << counter1 << endl;
    cout << counter2 << endl;
}

不幸的是,上面提到的代码没有按预期工作。我希望:

a) 总和总是等于 300
b) counter1 始终为 100
c) counter2 始终为 0

怎么了?

编辑:

当我在else 条件下调试sum 变量时,我看到如下值: 200、400、100 甚至 0(我认为加法甚至没有发生)。

【问题讨论】:

  • 读取值时还必须获取互斥锁。
  • 如果需要,请在阅读总和之前加入线程。
  • 这听起来不太好,因为您每次迭代都会杀死并重新生成线程,不是吗?
  • 为什么你希望sum 是300你知道线程已经完成之前?
  • 附带说明,您不应该手动锁定和解锁std::mutex。请改用std::lock_guardstd::scoped_lock,例如:{ std::lock_guard&lt;std::mutex&gt; lock(m); /* read-write sum as needed */ }

标签: c++ thread-safety mutex race-condition data-race


【解决方案1】:

C++ 互斥锁不起作用 - 同步失败

为什么第一次学习这些东西的每个人都假设适用于其他人的久经考验的同步原语被破坏了,而不是他们的假设?

互斥体很好。你的心智模式被打破了。这应该是您的开始假设。

我希望:

  1. 总和总是等于 300

如果您在检查值之前joined 两个线程 就会出现这种情况。但是您还没有这样做,因此您正在对sum 进行完全非同步的读取,而另外两个线程可能正在对其进行变异。这是一场数据竞赛。除非您在访问数据时始终使用互斥锁,否则互斥锁不会保护您的数据。

假设我们做了最小的更改,所以sum 始终受到保护:

    thread t1(addValue, 100); // a
    thread t2(addValue, 200); // b

    m.lock();
    if (sum == 300)           // c
    {
        counter1++;
    }
    else
    {
        counter2++;
    }
    sum = 0;
    m.unlock();

现在一些可用的订购是:

  1. abc - 你所期望的(如果你在阅读 sum 之前加入这两个线程可以保证什么)
  2. acb - 你在c 行读到100,递增counter2,第二个线程递增sum300 之后你读了它(但你从来没有看到这个)
  3. cab - 在这两个线程被安排运行之前,您立即阅读 0
  4. bca - 你读到200,在你检查后它后来增加到300

每一个排列都是允许的,除非你努力明确地对它们排序

【讨论】:

    【解决方案2】:

    它按预期工作,问题是您没有预料到所有 3 个线程的“时间”不会相同,并且您忽略了一个线程先于另一个线程开始的明显事情,这显然增加了一个优势,如果只需要循环 100 次增量。

    #include <iostream>
    #include <thread>
    #include <mutex>
    
    bool keep_alive;
    
    void add_value_mutex(std::mutex * mx, int * trg, int value) {
        while (keep_alive){
            mx->lock();
            (*trg) += value;
            mx->unlock();
        }
    }
    
    int main(){
    
        std::thread thread_1;
        std::thread thread_2;
    
        int count_targ = 2000;
        int * counter_1 = new int(0);
        int * counter_2 = new int(0);
    
        /* --- */
    
        std::mutex mx_1;
        std::mutex mx_2;
    
        keep_alive = true;
        thread_1 = std::thread(add_value_mutex, &mx_1, counter_1, 1);
        thread_2 = std::thread(add_value_mutex, &mx_2, counter_2, 1);
    
        while(1){
    
            if (mx_1.try_lock()){
                if (count_targ <= * counter_1){
                    mx_1.unlock();
                    break;
                }
                mx_1.unlock();
            }
    
            if (mx_2.try_lock()){
                if (count_targ <= * counter_2){
                    mx_2.unlock();
                    break;
                }
                mx_2.unlock();
            }
    
        }
        
        keep_alive = false;
        thread_1.join();
        thread_2.join();
            
        std::cout << "Thread 1 (independent mutex) -> " << * counter_1 << std::endl;
        std::cout << "Thread 2 (independent mutex) -> " << * counter_2 << std::endl;
    
        /* --- */
    
        keep_alive = true;
        (*counter_1) = 0;
        (*counter_2) = 0;
    
        std::mutex mx_s;
        thread_1 = std::thread(add_value_mutex, &mx_s, counter_1, 1);
        thread_2 = std::thread(add_value_mutex, &mx_s, counter_2, 1);
    
        while(1){
    
            if (mx_s.try_lock()){
                if (count_targ <= * counter_1 || count_targ <= * counter_2){
                    mx_s.unlock();
                    break;
                }
                mx_s.unlock();
            }
        }
    
        std::cout << "Thread 1 (shared mutex) -> " << * counter_1 << std::endl;
        std::cout << "Thread 2 (shared mutex) -> " << * counter_2 << std::endl;
    
        keep_alive = false;
        thread_1.join();
        thread_2.join();
    
    
        delete counter_1;
        delete counter_2;
    
        return 0;
    }
    

    如果你想要我的另一个例子来测量线程等待的时间check this one

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-10
      • 1970-01-01
      • 2014-05-02
      • 2016-05-19
      • 1970-01-01
      • 2017-11-21
      • 2012-08-28
      • 1970-01-01
      相关资源
      最近更新 更多