C++ 类和互斥体
假设我有一个类,它表示一种叫做foo的数据结构: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; } 现在
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