简体   繁体   English

为什么下面的程序在不使用互斥量的情况下没有混合output?

[英]Why the following program does not mix the output when mutex is not used?

I have made multiple runs of the program.我已经多次运行该程序。 I do not see that the output is incorrect, even though I do not use the mutex.即使我不使用互斥锁,我也没有看到 output 不正确。 My goal is to demonstrate the need of a mutex.我的目标是证明互斥体的需要。 My thinking is that different threads with different "num" values will be mixed.我的想法是具有不同“num”值的不同线程将混合在一起。

Is it because the objects are different?是因为对象不同吗?

using VecI = std::vector<int>;
class UseMutexInClassMethod {
    mutex m;
public:
    VecI compute(int num, VecI veci)
    {
        VecI v;
        num = 2 * num -1;
        for (auto &x:veci) {
            v.emplace_back(pow(x,num));
            std::this_thread::sleep_for(std::chrono::seconds(1));

        }
        return v;
    }

};  

void TestUseMutexInClassMethodUsingAsync()
{
    const int nthreads = 5;
    UseMutexInClassMethod useMutexInClassMethod;
    VecI vec{ 1,2,3,4,5 };
    std::vector<std::future<VecI>> futures(nthreads);
    std::vector<VecI> outputs(nthreads);

    for (decltype(futures)::size_type i = 0; i < nthreads; ++i) {
        futures[i] = std::async(&UseMutexInClassMethod::compute,
            &useMutexInClassMethod,
            i,vec
        );
    }

    for (decltype(futures)::size_type i = 0; i < nthreads; ++i) {
        outputs[i] = futures[i].get();
        for (auto& x : outputs[i])
            cout << x << " ";
        cout << endl;
    }

}

If you want an example that does fail with a high degree of certainty you can look at the below.如果您想要一个非常确定地失败的示例,您可以查看以下内容。 It sets up a variable called accumulator to be shared by reference to all the futures.它设置了一个名为accumulator的变量,通过引用所有期货来共享 This is what is missing in your example.这是您的示例中缺少的内容。 You are not actually sharing any memory.您实际上并未共享任何 memory。 Make sure you understand the difference between passing by reference and passing by value .确保您了解按引用传递和按值传递之间的区别。

#include <vector>
#include <memory>
#include <thread>
#include <future>
#include <iostream>
#include <cmath>
#include <mutex>


struct UseMutex{
    int compute(std::mutex & m, int & num)
    {
        for(size_t j = 0;j<1000;j++)
        {
            ///////////////////////
            // CRITICAL SECTIION //
            ///////////////////////

            // this code currently doesn't trigger the exception
            // because of the lock on the mutex. If you comment
            // out the single line below then the exception *may*
            // get called.
            std::scoped_lock lock{m};

            num++;
            std::this_thread::sleep_for(std::chrono::nanoseconds(1));
            num++;
            if(num%2!=0)
                throw std::runtime_error("bad things happened");
        }

        return 0;
    }
};  

template <typename T> struct F;

void TestUseMutexInClassMethodUsingAsync()
{
  
    const int nthreads = 16;
    int accumulator=0;
    std::mutex m;
    std::vector<UseMutex> vs{nthreads};
    std::vector<std::future<int>> futures(nthreads);


    for (auto i = 0; i < nthreads; ++i) {
        futures[i]= std::async([&,i](){return vs[i].compute(m,accumulator);});
    }

    for(auto i = 0; i < nthreads; ++i){
        futures[i].get(); 
    }


}

int main(){
    TestUseMutexInClassMethodUsingAsync();
}

You can comment / uncomment the line您可以评论/取消评论该行

std::scoped_lock lock{m};

which protects the increment of the shared variable num .它保护了共享变量num的增量。 The rule for this mini program is that at the line这个小程序的规则是在线

 if(num%2!=0)
                throw std::runtime_error("bad things happened");

num should be a multiple of two. num应该是二的倍数。 But as multiple threads are accessing this variable without a lock you can't guarantee this.但是由于多个线程在没有锁的情况下访问这个变量,你不能保证这一点。 However if you add a lock around the double increment and test then you can be sure no other thread is accessing this memory during the duration of the increment and test.但是,如果您在双增量和测试周围添加一个锁,那么您可以确定在增量和测试期间没有其他线程正在访问此 memory。

Failing https://godbolt.org/z/sojcs1WK9失败https://godbolt.org/z/sojcs1WK9

Passing https://godbolt.org/z/sGdx3x3q3通过https://godbolt.org/z/sGdx3x3q3

Of course the failing one is not guaranteed to fail but I've set it up so that it has a high probability of failing.当然,失败的人不能保证失败,但我已经设置了它,所以它很有可能失败。

Notes笔记

[&,i](){return vs[i].compute(m,accumulator);};

is a lambda or inline function.是 lambda 或内联 function。 The notation [&,i] means it captures everything by reference except i which it captures by value.符号[&,i]表示它通过引用捕获除i之外的所有内容,它通过值捕获。 This is important because i changes on each loop iteration and we want each future to get a unique value of i这很重要,因为i在每次循环迭代中都会发生变化,并且我们希望每个未来都获得i的唯一值

Is it because the objects are different?是因为对象不同吗?

Yes.是的。

Your code is actually perfectly thread safe, no need for mutex here.您的代码实际上是完全线程安全的,这里不需要mutex You never share any state between threads except for copying vec from TestUseMutexInClassMethodUsingAsync to compute by std::async (and copying is thread-safe) and moving computation result from compute 's return value to futures[i].get() 's return value.您永远不会在线程之间共享任何 state,除了将vecTestUseMutexInClassMethodUsingAsync复制到std::async compute (并且复制是线程安全的)并将计算结果从compute的返回值移动到futures[i].get()的返回价值。 .get() is also thread-safe: it blocks until the compute() method terminates and then returns its computation result. .get()也是线程安全的:它阻塞直到compute()方法终止,然后返回其计算结果。

It's actually nice to see that even a deliberate attempt to get a race condition failed:)很高兴看到即使是故意尝试获得竞争条件也失败了:)

You probably have to fully redo your example to demonstrate is how simultaneous* access to a shared object breaks things.您可能必须完全重做您的示例来演示同时*访问共享 object 是如何破坏事情的。 Get rid of std::async and std::future , use simple std::thread with capture-by-reference, remove sleep_for (so both threads do a lot of operations instead of one per second), significantly increase number of operations and you will get a visible race.摆脱std::asyncstd::future ,使用简单的std::thread引用捕获,删除sleep_for (因此两个线程都执行大量操作而不是每秒一个),显着增加操作数量和你会看到一场比赛。 It may look like a crash, though.不过,它可能看起来像一场崩溃。

* - yes, I'm aware that "wall-clock simulateneous access" does not exist in multithreaded systems, strictly speaking. * - 是的,我知道严格来说,多线程系统中不存在“挂钟模拟访问”。 However, it helps getting a rough idea of where to look for visible race conditions for demonstration purposes.但是,它有助于大致了解在哪里寻找可见的竞争条件以用于演示目的。

Comments have called out the fact that just not protecting a critical section does not guarantee that the risked behavior actually occurs.评论指出,仅仅不保护关键部分并不能保证风险行为确实发生。
That also applies for multiple runs, because while you are not allowed to test a few times and then rely on the repeatedly observed behavior, it is likely that optimization mechanisms cause a likely enough reoccurring observation as to be perceived has reproducible.这也适用于多次运行,因为虽然不允许您进行几次测试然后依赖于反复观察到的行为,但优化机制很可能会导致足够重复的观察结果被认为具有可重复性。

If you intend to demonstrate the need for synchronization you need to employ synchronization to poise things to a near guaranteed misbehavior of observable lack of protection.如果您打算证明同步的必要性,您需要使用同步来使事情平衡到几乎可以保证的可观察到的缺乏保护的不当行为。

Allow me to only outline a sequence for that, with a few assumptions on scheduling mechanisms (this is based on a rather simple, single core, priority based scheduling environment I have encountered in an embedded environment I was using professionally), just to give an insight with a simplified example:请允许我仅概述一个序列,并对调度机制进行一些假设(这是基于我在专业使用的嵌入式环境中遇到的一个相当简单的、单核、基于优先级的调度环境),只是为了给出一个用一个简化的例子来洞察:

  • start a lower priority context.启动较低优先级的上下文。
  • optionally set up proper protection before entering the critical section可选择在进入临界区之前设置适当的保护
  • start critical section, eg by outputting the first half of to-be-continuous output开始临界区,例如通过输出待连续 output 的前半部分
  • asynchronously trigger a higher priority context, which is doing that which can violate your critical section, eg outputs something which should not be in the middle of the two-part output of the critical section异步触发更高优先级的上下文,这可能会违反您的关键部分,例如输出不应位于关键部分的两部分 output 中间的内容
  • (in protected case the other context is not executed, in spite of being higher priority) (在受保护的情况下,尽管优先级更高,但不执行其他上下文)
  • (in unprotected case the other context is now executed, because of being higher priority) (在不受保护的情况下,现在执行其他上下文,因为优先级更高)
  • end critical section, eg by outputting the second half of the to-be-continuous output结束临界区,例如通过输出待连续 output 的后半部分
  • optionally remove the protection after leaving the critical section离开临界区后可选择移除保护
  • (in protected case the other context is now executed, now that it is allowed) (在受保护的情况下,现在执行另一个上下文,现在它是允许的)
  • (in unprotected case the other context was already executed) (在不受保护的情况下,其他上下文已经执行)

Note:笔记:
I am using the term "critical section" with the meaning of a piece of code which is vulnerable to being interrupted/preempted/descheduled by another piece of code or another execution of the same code.我使用术语“关键部分”来表示一段代码,该代码段容易被另一段代码或同一代码的另一次执行中断/抢占/取消调度。 Specifically for me a critical section can exist without applied protection, though that is not a good thing.特别是对我来说,关键部分可以在没有应用保护的情况下存在,尽管这不是一件好事。 I state this explicitly because I am aware of the term being used with the meaning "piece of code inside applied protection/synchronization".我明确指出了 state,因为我知道该术语的含义是“应用保护/同步中的一段代码”。 I disagree but I accept that the term is used differently and requires clarification in case of potential conflicts.我不同意,但我接受该术语的使用方式不同,并且需要在潜在冲突的情况下进行澄清。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM