我的应用程序有一个基本上是普通客户端的IRC模块。由于这是严格的线程,我冒着插件检索的风险,例如,用户昵称 - 它当时有效,但解析器触发更新,更改所述昵称。 一旦另一个线程再次执行,它就会处理一个指向现在无效内存的指针,因为它不可能将return + copy作为原子操作。
基于以下代码,我的假设是否正确?因此,我想我必须使用通常的互斥锁定/解锁方法,除非有人可以确认或建议否则(我宁愿不必转换并返回shared_ptr,但我想这是一个有效的选项,它是只是我打算SWIG这个并且不知道它是否不喜欢它们。)
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);
}
...
}
答案 0 :(得分:8)
我建议锁定这种细粒度的水平很可能(方式)过度杀伤。
我建议对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);
}
}
答案 1 :(得分:4)
代码具有竞争条件,因此具有未定义的行为,因为在同一对象上存在潜在的读取(->get()
)和写入(.reset()
或=
)( <{1}}实例)来自不同的线程:必须同步对std::shared_ptr<std::string>
的访问。
注意在getter中锁定std::mutex
并返回std::shared_ptr
是不够的,因为getter的调用者将使用锁外的c_str()
结果:getter需要按值返回c_str()
。
要更正:
将shared_ptr
添加到std::mutex
(请注意,这会使该类无法复制):
IrcUser
将mutable std::mutex mtx_; // Must be mutable for use within 'const'
锁定在getter和std::mutex
中,使用std::lock_guard
来确保异常安全:
Update()
如果复制可以接受,请考虑使用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
可能会增加不必要的复杂性。
答案 2 :(得分:1)
是的,昵称可能会导致竞争条件导致您的getter访问内存不足。 shared_ptrs在所有权语义方面只是线程安全的。您需要为其值添加某种形式的同步。