考虑以下 C++11 代码,其中类 B
被实例化并由多个线程使用。因为 B
修改了一个共享 vector ,所以我必须在 B
的构造函数和成员函数 foo 中锁定对它的访问。为了初始化成员变量 id
,我使用了一个计数器,它是一个原子变量,因为我从多个线程访问它。
struct A {
A(size_t id, std::string const& sig) : id{id}, signature{sig} {}
private:
size_t id;
std::string signature;
};
namespace N {
std::atomic<size_t> counter{0};
typedef std::vector<A> As;
std::vector<As> sharedResource;
std::mutex barrier;
struct B {
B() : id(++counter) {
std::lock_guard<std::mutex> lock(barrier);
sharedResource.push_back(As{});
sharedResource[id].push_back(A("B()", id));
}
void foo() {
std::lock_guard<std::mutex> lock(barrier);
sharedResource[id].push_back(A("foo()", id));
}
private:
const size_t id;
};
}
不幸的是,这段代码包含竞争条件并且不能像这样工作(有时 ctor 和 foo() 不使用相同的 id)。如果我将 id 的初始化移动到被互斥锁锁定的 ctor 主体,它会起作用:
struct B {
B() {
std::lock_guard<std::mutex> lock(barrier);
id = ++counter; // counter does not have to be an atomic variable and id cannot be const anymore
sharedResource.push_back(As{});
sharedResource[id].push_back(A("B()", id));
}
};
你能帮我理解为什么后一个例子有效吗(是因为它没有使用相同的互斥锁吗?)?有没有一种安全的方法可以在 B
的初始化列表中初始化 id
而无需将其锁定在 ctor 的主体中?我的要求是 id
必须是 const
并且 id
的初始化发生在初始化列表中。
最佳答案
首先,发布的代码中仍然存在一个基本的逻辑问题。
您使用 ++ counter
作为 id
。考虑 B
的第一个创建,
在一个线程中。 B
将有 id == 1
;在 push_back
之后
sharedResource
,您将拥有 sharedResource.size() == 1
,并且
访问它的唯一合法索引将是 0
。
此外,代码中存在明显的竞争条件。即使你
更正上述问题(用counter++
初始化id
),假设
counter
和 sharedResource.size()
当前都是 0
;
你刚刚初始化。线程一进入B
的构造函数,
递增 counter
,所以:
counter == 1
sharedResource.size() == 0
然后它被线程 2 中断(在它获取互斥锁之前),这
也递增 counter
(到 2),并使用它之前的值 (1) 作为
编号
。然而,在线程 2 中的 push_back
之后,我们只有
sharedResource.size() == 1
,唯一合法的索引是0。
在实践中,我会避免使用两个单独的变量(counter
和
sharedResource.size()
) 应具有相同的值。从
经验:应该相同的两件事不会——唯一的
应该使用时间冗余信息是当它用于
控制;即在某些时候,你有一个 assert( id ==
sharedResource.size() )
,或类似的东西。我会使用类似的东西:
B::B()
{
std::lock_guard<std::mutex> lock( barrier );
id = sharedResource.size();
sharedResource.push_back( As() );
// ...
}
或者如果你想使 id
为常量:
struct B
{
static int getNewId()
{
std::lock_guard<std::mutex> lock( barrier );
int results = sharedResource.size();
sharedResource.push_back( As() );
return results;
}
B::B() : id( getNewId() )
{
std::lock_guard<std::mutex> lock( barrier );
// ...
}
};
(请注意,这需要获取互斥量两次。或者,您
可以传递完成更新所需的附加信息
sharedResource
到 getNewId()
,并让它完成整个工作。)
关于c++ - C++中原子变量的线程安全初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10017357/