考虑以下简单代码:
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::unlock
和std::thread
,要么我对多线程的基本理解缺乏一些基本知识。
问题是 - 我的逻辑错在哪里?
如何使用辅助类和两个具有相同对象成员函数的val
来解决此问题?
是否可以保证val
的增量和打印都会使用这种逻辑一个接一个地出现?即,res.json()
在显示之前是否会增加两次,反之亦然?
答案 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();
}