在std :: thread中使用无限循环来递增和显示值

时间:2017-12-03 02:08:55

标签: c++ multithreading c++11 mutex

考虑以下简单代码:

using ms = std::chrono::milliseconds;
int val = 0;
for(;;)
{
    std::cout << val++ << ' ';
    std::this_thread::sleep_for(ms(200));
}

我们看到我们每0.2秒无限打印后续数字。

现在,我想使用辅助类和多线程实现相同的逻辑。我的目标是能够运行类似的东西:

int main()
{
    Foo f;
    std::thread t1(&Foo::inc, f);
    std::thread t2(&Foo::dis, f);
    t1.join();
    t2.join();
}

其中Foo::inc()val f增加对象1的成员变量Foo::dis()std::mutex将显示相同变量

由于最初的想法包括无限递增和打印值,我假设这两个函数都必须包含无限循环。可能出现的问题是数据争用 - 读取和递增相同的变量。为了防止这种情况,我决定使用Foo

我实施class Foo { int val; public: Foo() : val{0} {} void inc() { for(;;){ mtx.lock(); ++val; mtx.unlock(); } } void dis() { using ms = std::chrono::milliseconds; for(;;){ mtx.lock(); std::cout << val << ' '; std::this_thread::sleep_for(ms(200)); mtx.unlock(); } } }; 的想法如下:

mtx

显然它错过了std::mutex mtx; 对象,所以行

#include

写在mtx下面,将main()声明为全局变量。

根据我的理解,结合这门课程&#39;使用上述val函数的定义应该发出两个独立的无限循环,每个循环首先锁定互斥锁,增加或显示0 1 2 3 4...并解锁互斥锁,以便另一个可以执行第二个动作即可。

实际发生的事情不是显示0 0 0 0 0...的序列,而是显示std::mutex::lock。我的猜测是我要么错误地使用std::mutex::unlockstd::thread,要么我对多线程的基本理解缺乏一些基本知识。

问题是 - 我的逻辑错在哪里?

  • 如何使用辅助类和两个具有相同对象成员函数的val来解决此问题?

  • 是否可以保证val的增量和打印都会使用这种逻辑一个接一个地出现?即,res.json()在显示之前是否会增加两次,反之亦然?

3 个答案:

答案 0 :(得分:2)

使用成员函数启动线程时,需要传递对象的地址,而不是对象本身

std::thread t2(&Foo::dis, &f);

请注意,这仍然不会打印1 2 3 4 ..您需要完成增量操作和打印备用。

#include <thread>
#include<iostream>
#include <mutex>
std::mutex mtx1, mtx2;

class Foo {
    int val;
public:
    Foo() : val{0} { mtx2.lock(); }
    void inc()
    {
        for(;;){
            mtx1.lock();
            ++val;
            mtx2.unlock();
        }
    }
    void dis()
    {
        using ms = std::chrono::milliseconds;
        for(;;){
            mtx2.lock();
            std::cout << val <<std::endl;
            std::this_thread::sleep_for(ms(200));
            mtx1.unlock();
        }
    }
};

int main()
{
    Foo f;
    std::thread t1(&Foo::inc, &f);
    std::thread t2(&Foo::dis, &f);
    t1.join();
    t2.join();
}

另请查看http://en.cppreference.com/w/cpp/thread/condition_variable

答案 1 :(得分:2)

互斥锁不是信号。这不公平。您可以解锁然后重新锁定互斥锁,等待它的人永远不会注意到。

它保证只有一个线程锁定它。

你的任务,将它分成两个线程,似乎完全没有意义。使用睡眠也是一个坏主意,因为打印需要不明的时间,使显示之间的时间间隔漂移不可预测。

您可能(A)不想这样做,并且失败(B)使用条件变量。一个线程每X次递增该值(基于固定的开始时间,而不是基于X的延迟),然后对条件变量进行签名。它在等待时不会持有互斥锁。

另一个线程等待条件变量并且计数器值改变。当它唤醒时,它会复制计数器,解锁,打印一次,更新最后看到的值,然后再次等待条件变量(和值变化)。

对此的一个温和的好处是,如果io非常缓慢或阻塞,计数器会不断增加,因此其他消费者可以使用它。

struct Counting {
  int val = -1; // optionally atomic
  std::mutex mtx;
  std::condition_variable cv;

  void counting() {
    while(true){
      {
        auto l=std::unique_lock<std::mutex>(mtx);
        ++val; // even if atomic, val must be modified while or before the mtx is held and before the notify.
      }
      // or notify all:
      cv.notify_one(); // no need to hold lock here
      using namespace std::literals;
      std::this_thread::sleep_for(200ms); // ideally wait to an absolute time instead of delay here
    }
  }

  void printing() {
    int old_val=-1;
    while(true){
      int new_val=[&]{
        auto lock=std::unique_lock<std::mutex>(mtx);

        cv.wait(lock, [&]{ return val!=old_val; }); // only print if we have a new value
        return val;
      }();// release lock, no need to hold it while printing

      std::cout << new_val << std::endl; // endl flushes.  Note there are threading issues streaming to cout like this.
      old_val=new_val; // update last printed value
    }
  }
};

如果一个线程正在打印另一个计数,你基本上可以得到你想要的。

答案 2 :(得分:2)

你正在睡觉时线程被锁定,导致其他线程无法在大部分时间内运行。

void dis()
{
    using ms = std::chrono::milliseconds;
    for(;;){
        mtx.lock();
        std::cout << val << ' ';
        std::this_thread::sleep_for(ms(200)); // this is still blocking the other thread
        mtx.unlock();
    }
}

试试这个:

void dis()
{
    using ms = std::chrono::milliseconds;
    for(;;){
        mtx.lock();
        std::cout << val << ' ';
        mtx.unlock(); // unlock to allow the other thread to progress
        std::this_thread::sleep_for(ms(200));
    }
}

此外,您可以将其添加为班级的成员,而不是使用全局std::mutex

如果你想同步线程以产生偶数输出的数字,每次只增加一个,那么你需要像std::condition_variable这样的东西,这样每个线程都可以在完成它时发出信号。作业(第一个线程 - 递增和第二个线程 - 打印)。

以下是一个例子:

class Foo {
    int val;
    std::mutex mtx;
    std::condition_variable cv;
    bool new_value; // flag when a new value is ready

public:
    Foo() : val{0}, new_value{false} {}

    void inc()
    {
        for(;;){
            std::unique_lock<std::mutex> lock(mtx);

            // release the lock and wait until new_value has been consumed
            cv.wait(lock, [this]{ return !new_value; }); // wait for change in new_value

            ++val;
            new_value = true; // signal for the other thread there is a new value

            cv.notify_one(); // wake up the other thread
        }
    }

    void dis()
    {
        using ms = std::chrono::milliseconds;
        for(;;){
            // a nice delay
            std::this_thread::sleep_for(ms(200));

            std::unique_lock<std::mutex> lock(mtx);

            // release the lock and wait until new_value has been produced
            cv.wait(lock, [this]{ return new_value; }); // wait for a new value

            std::cout << val << ' ' << std::flush; // don't forget to flush
            new_value = false; // signal for the other thread that the new value was used

            cv.notify_one(); // wake up the other thread
        }
    }
};

int main(int argc, char** argv)
{
    Foo f;

    std::thread t1(&Foo::inc, &f);
    std::thread t2(&Foo::dis, &f);

    t1.join();
    t2.join();
}