C++ 类和互斥体

C++ 类和互斥体,c++,multithreading,c++11,mutex,C++,Multithreading,C++11,Mutex,假设我有一个类,它表示一种叫做foo的数据结构: class foo{ public: foo(){ attr01 = 0; } void f(){ attr01 += 5; } private: int attr01; }; class fooSingleThreadUserClass{ void usefoo(){ fooAttr.f(); } foo fooAttr; } 现在

假设我有一个类,它表示一种叫做foo的数据结构:

class foo{
  public:
    foo(){
      attr01 = 0;
    }
    void f(){
      attr01 += 5;
    }
  private:
    int attr01;
};

class fooSingleThreadUserClass{
    void usefoo(){
      fooAttr.f();
    }

    foo fooAttr;
}
现在假设在软件构建的后面,我发现我需要多线程。我应该在foo中添加互斥吗

class foo{
  public:
    foo(){
      attr01 = 0;
    }
    void f(){
      attr01Mutex.lock();
      attr01 += 5;
      attr01Mutex.unlock();
    }
  private:
    int attr01;
    std::mutex attr01Mutex;
};

class fooMultiThreadUserClass{
    void usefoo(){
      std::thread t1(&fooMultiThreadUserClass::useFooWorker, this);
      std::thread t2(&fooMultiThreadUserClass::useFooWorker, this);
      std::thread t3(&fooMultiThreadUserClass::useFooWorker, this);
      std::thread t4(&fooMultiThreadUserClass::useFooWorker, this);

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

    void useFooWorker(){
      fooAttr.f();
    }

    foo fooAttr;
}
我知道FoomulthreadUserClass现在可以在高性能的情况下运行foo而无需竞争,但是FoomSingleThreadUserClass会因为互斥开销而降低性能吗?我很想知道。或者我应该出于并发目的从foo派生fooCC,以便foosinglethreadusercla可以继续使用没有互斥的foo,而foomulthreaduserclass使用带有互斥的fooCC,如下所示

class fooCC : public foo{
  public:
    foo(){
      attr01 = 0;
    }
    void f(){  // I assume that foo::f() is now a virtual function.
      attr01Mutex.lock();
      foo::f();
      attr01Mutex.unlock();
    }
  private:
    std::mutex attr01Mutex;
};
还假设编译器优化已经处理了虚拟调度。我想知道我应该使用inhertance还是简单地将互斥锁放在原始类中

我已经搜索过Stackoverflow了,但我想我的问题有点太具体了


编辑:注意,不必只有一个参数,这个问题应该是抽象的,有一类n个参数。

使用
std::lock\u guard
lock\u-guard
在其构造函数中接受一个
mutex
。在构造过程中,
lock\u-guard
锁定
mutex
。当
lock\u-guard
超出范围时,其析构函数会自动释放锁

class foo
{
private:
  std::mutex mutex;
  int attr01;

public:
  foo() {
    attr01 = 0;
  }

  void f(){
    std::lock_guard<std::mutex> lock (mutex);
    attr01 += 5;
  }
};
class-foo
{
私人:
std::互斥互斥;
int attr01;
公众:
foo(){
attr01=0;
}
void f(){
std::锁和保护锁(互斥锁);
attr01+=5;
}
};
如果需要从
const
函数锁定或解锁
mutex
,可以将
mutable
放在
mutex
上。我通常将
mutable
关闭
mutex
,直到我特别需要它

它会失去性能吗?视情况而定。如果要调用该函数一百万次,那么创建
互斥体的开销可能会成为一个问题(它们并不便宜)。如果函数需要很长时间才能执行,并且它经常被许多线程调用,那么快速阻塞可能会影响性能。如果您无法确定具体问题,只需使用
std::lock\u guard


Hans Passant提出了一个合理的问题,超出了您的问题范围。我想赫伯·萨特(?)在他的一篇网站文章中写到了这一点。不幸的是,我现在找不到它。要理解为什么多线程如此困难,为什么锁定单个数据字段“还不够”,读一本关于多线程编程的书,比如每对象互斥有时是个好主意,但这种方法不是模块化的。考虑这个例子:

using namespace std;

struct LimitCounter {
    int balance = 1000;
    mutex lock;
    bool done() const { 
        lock_guard<mutex> g(lock);
        return balance == 0; 
    }

    void dec() {
        lock_guard<mutex> g(lock);
        balance--;
    }
};
使用名称空间std;
结构限时计数器{
整数余额=1000;
互斥锁;
bool done()常量{
锁(g)(锁);;
收益余额==0;
}
void dec(){
锁(g)(锁);;
平衡--;
}
};
和此限制计数器的用户:

LimitCounter counter;  // global context

// JobRunner run some job no more than 1000 times
struct JobRunner {
    motex lock;
    void do_the_job() {
        lock_guard<mutex> g(lock);
        if (!counter.done()) {
            ...actually do the job...
        }
        counter.dec();
    }
};
LimitCounter计数器;//全球环境
//JobRunner运行某些作业的次数不超过1000次
结构作业管理器{
莫特克斯锁;
void do___job(){
锁(g)(锁);;
如果(!counter.done()){
…实际上是在做这项工作。。。
}
counter.dec();
}
};
此代码是线程安全的,但不正确(在多线程环境中,平衡可能变为负,作业将执行1000次以上)。两个正确同步的对象的组合不会给出正确的结果

要使其正确,必须在JobRunner类的所有实例之间共享锁。它必须在counter.done()检查之前锁定,在counter.dec()之后解锁。换句话说,锁层次结构必须与对象层次结构解耦。 把锁放在哪里是个人喜好的问题。可以在JobRunner::do_作业内锁定LimitCounter::lock,也可以使JobRunner::lock成为静态变量,可以将互斥量作为参数传递给JobRunner::do_作业

另一种情况是,当您拥有大量对象时。在这种情况下,您不能只为每个对象添加一个互斥对象,因为它太昂贵(每个互斥对象都是内核对象,您可能会用完句柄)。在这种情况下,您可以切分对象,并使用相同的互斥锁锁定每个切分。例如:

mutex mutexes[0x1000]; 
....
struct UbiquitousResource {
    int unique_id;
    void do_some_job() {
        auto& m = mutexes[hash(unique_id) & 0xFFF];
        lock_guard<mutex> g(m);
        ...do the job...
    }
};
mutex互斥体[0x1000];
....
结构泛素资源{
int-unique_-id;
void dou_一些工作(){
auto&m=mutexes[hash(unique_id)&0xFFF];
锁紧护罩g(m);
…做这项工作。。。
}
};

我相信Java和C在使用同步方法(在Java中)或锁定对象(在C中)时会做类似的事情。

首先,你应该使用
std::lock\u guard
来锁定你的互斥锁,并可能使你的互斥锁
可变
。如果它只是一个
int
,您可以使用
std::atomic_int
。无竞争的互斥锁通常非常便宜。我会将它直接添加到类中,并且只有在遇到性能问题时才进行优化(并且确信这是互斥锁使事情变慢了)。真正的类并不只有一个数据成员。确保对象始终处于一致状态,并且所有数据成员都是同步的,这永远无法在类本身中实现。它必须由客户端代码完成。把工作交给最不可能做对的程序员。穿线很难,我们去购物吧。@HansPassant,我不同意。在对象级别创建它是最简单的方法。但是,所有操作数据的函数都应该有一个
std::lock_guard
,以确保数据保持同步。在这种情况下,您需要同步多个对象,但是必须在对象之外实现它,这会使它变得更加复杂(至少在我的实践中,同步一堆对象要困难得多,除非它们本身是另一个对象的一部分,而另一个对象本身可以在每个公共函数的基础上进行同步。)?干杯,我以前从未有过新的lock_guard