我的应用程序有一个IRC模块,本质上是一个普通的客户端。 由于这是高度线程化,我站在风险的插件检索,例如,用户的绰号 - 它是有效的时候,但是解析器触发更新,改变上述昵称。 一旦其他线程又有执行力,它在处理指针现在已经无效的内存,因为这将是不可能有回报+副本作为一个原子操作。
是我这个正确的假设的基础上,下面的代码? 因此,我想我不得不使用通常的互斥锁定/解锁方法,除非有人可以确认或提出其他方式(我宁愿没有进行转换,并返回一个shared_ptr,但我想这是一个有效的选择,它的只是我打算在这SWIG'ing,不知道是否会不喜欢他们)。
IrcUser.h
class IrcUser : public IrcSubject
{
private:
...
std::shared_ptr<std::string> _nickname;
std::shared_ptr<std::string> _ident;
std::shared_ptr<std::string> _hostmask;
public:
...
const c8*
Ident() const
{ return _ident.get()->c_str(); }
const c8*
Hostmask() const
{ return _hostmask.get()->c_str(); }
const u16
Modes() const
{ return _modes; }
const c8*
Nickname() const
{ return _nickname.get()->c_str(); }
bool
Update(
const c8 *new_nickname,
const c8 *new_ident,
const c8 *new_hostmask,
const mode_update *new_modes
);
};
IrcUser.cc
bool
IrcUser::Update(
const c8 *new_nickname,
const c8 *new_ident,
const c8 *new_hostmask,
const mode_update *new_modes
)
{
if ( new_nickname != nullptr )
{
if ( _nickname == nullptr )
{
*_nickname = std::string(new_nickname);
}
else
{
_nickname.reset();
*_nickname = std::string(new_nickname);
}
Notify(SN_NicknameChange, new_nickname);
}
...
}
该代码具有竞争条件,因此未定义的行为,因为存在潜在的读取(在->get()
和写入(所述.reset()
或=
相同的对象上)(一个std::shared_ptr<std::string>
实例)从单独的线程:访问std::shared_ptr
s必须是同步的。
注意锁定std::mutex
,吸气内并返回c_str()
是不够的吸气剂呼叫者将使用的结果c_str()
锁之外:吸气需要返回shared_ptr
的值。
纠正:
添加std::mutex
,以IrcUser
(注意,这使得现在的类不可复制的):
mutable std::mutex mtx_; // Must be mutable for use within 'const'
锁定std::mutex
的getter和Update()
使用std::lock_guard
异常安全:
std::shared_ptr<std::string> Nickname() const { std::lock_guard<std::mutex> l(mtx_); return _nickname; } bool IrcUser::Update(const c8 *new_nickname, const c8 *new_ident, const c8 *new_hostmask, const mode_update *new_modes) { if (new_nickname) { { std::lock_guard<std::mutex> l(mtx_); _nickname.reset(new std::string(new_nickname)); } // No reason to hold the lock here. Notify(SN_NicknameChange, new_nickname); } return true; }
考虑只使用std::string
如果复制是可以接受的作为shared_ptr
可能会增加不必要的复杂性。
我建议锁定在这样的细粒化的水平是可能的(方式)矫枉过正。
我建议做原子更新到IrcUser对象本身,这可能取决于你的库实现与目标架构是无锁的 。 下面是一个使用一个样本
-
std::atomic_is_lock_free<std::shared_ptr>
-
std::atomic_load<std::shared_ptr>
-
std::atomic_store<std::shared_ptr>
见http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic的文档。
免责声明 我不知道有多少的编译器/ C ++库实现已经实现了这个C ++ 11功能。
下面是它会是什么样子:
#include <atomic>
#include <memory>
#include <string>
struct IrcSubject {};
typedef char c8;
typedef uint16_t u16;
typedef u16 mode_update;
class IrcUser : public IrcSubject
{
private:
// ...
std::string _nickname;
std::string _ident;
std::string _hostmask;
u16 _modes;
public:
IrcUser(std::string nickname, std::string ident, std::string hostmask, u16 modes)
: _nickname(nickname), _ident(ident), _hostmask(hostmask), _modes(modes) { }
// ...
std::string const& Ident() const { return _ident; }
std::string const& Hostmask() const { return _hostmask; }
const u16 Modes() const { return _modes; }
std::string const& Nickname() const { return _nickname; }
};
//IrcUser.cc
bool Update(std::shared_ptr<IrcUser>& user,
std::string new_nickname,
std::string new_ident,
std::string new_hostmask,
const mode_update *new_modes
)
{
auto new_usr = std::make_shared<IrcUser>(std::move(new_nickname), std::move(new_ident), std::move(new_hostmask), *new_modes /* ??? */);
std::atomic_store(&user, new_usr);
//Notify(SN_NicknameChange, new_nickname);
return true;
}
bool Foo(IrcUser const& user)
{
// no need for locking, user is thread safe
}
int main()
{
auto user = std::make_shared<IrcUser>("nick", "ident", "hostmask", 0x1e);
mode_update no_clue = 0x04;
Update(user, "Nick", "Ident", "Hostmask", &no_clue);
{
auto keepref = std::atomic_load(&user);
Foo(*keepref);
}
}
是的,可以用昵称导致你的干将访问记性不好出现争用条件。 shared_ptrs只用螺纹关于其所有权symantics安全。 您将需要添加某种形式的同步他们的价值观。