以线程安全的方式与外界共享数据成员
我真的很感激有关此事的一些建议。
例如
class Foo
{
TData data;
public:
TData *getData() { return &data; } // How can we do this in a thread safe manner ?
};
,所以我想要一种机制来使 getData()
线程安全。我提出了自己的解决方案,其中涉及将数据成员打包在以下模板类中,并使用用于同步对其访问的互斥体。你怎么认为 ?可能出现什么问题?
class locked_object : boost::noncopyable
{
T *object;
TLockable *lock;
bool locked;
public:
locked_object(T *_object, TLockable *_lock) : object(_object), lock(_lock), locked(true)
{
lock->lock();
}
~locked_object()
{
lock->unlock();
}
T *get()
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
void unlock()
{
locked = false;
lock->unlock();
}
T *operator ->() const
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
operator T *() const
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
};
感谢您提前提出任何评论和意见。
法提赫
I would really appreciate some advise on this matter.
e.g.
class Foo
{
TData data;
public:
TData *getData() { return &data; } // How can we do this in a thread safe manner ?
};
So I want to have a mechanism to make getData()
thread-safe. I have come up with my own solution which involves packing the data member in the following template class with a mutex used to synchronize access to it. What do you think ? What might be the possible problems ?
class locked_object : boost::noncopyable
{
T *object;
TLockable *lock;
bool locked;
public:
locked_object(T *_object, TLockable *_lock) : object(_object), lock(_lock), locked(true)
{
lock->lock();
}
~locked_object()
{
lock->unlock();
}
T *get()
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
void unlock()
{
locked = false;
lock->unlock();
}
T *operator ->() const
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
operator T *() const
{
_ASSERT(locked);
if (!locked)
throw new std::exception("Synchronization error ! Object lock is already released !");
return this->tobject;
}
};
Thank you for any comments and opinions in advance.
Fatih
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您听说过德墨忒尔法则吗?
有一个类似的建议(我认为来自萨特):不要共享对内部的引用
两者都是为了避免耦合,因为通过共享对内部的引用,这意味着您的公共接口泄漏了实现细节。
既然这么说,你的界面就不起作用了。
问题是您锁定的是代理,而不是对象:我仍然可以通过多个路径访问:
Foo
,不需要互斥体 -->奥普斯?locked_object
-->这似乎不是故意的...更重要的是,一般来说,您不能锁定对象的单个部分,因为这样您就无法拥有整个对象的事务语义。
Have you ever heard of The Law of Demeter ?
There is a similar advise (from Sutter I think): Don't share references to your internals
Both are meant to avoid coupling, because by sharing a reference to your internal, it means that your public interface leaks an implementation detail.
Now that this is said, your interface does not work.
The problem is that your are locking the proxy, not the object: I can still access through multiple paths:
Foo
, no mutex required --> oups ?locked_object
--> this doesn't seem intentional...More importantly, you cannot, in general, lock a single part of an object, because then you cannot have transactional semantics for the object as a whole.
您的设计让用户有责任确保在正确的时间锁定和解锁对象。即使您锁定的对象进行了错误检查,它也没有涵盖所有基础(例如在完成对象时忘记释放该对象),
所以可以说您有不安全的对象
TData
。您将其包装在Foo
中,但不是Foo
返回指向TData
的指针,而是重新实现TData
中的所有公共方法在Foo
中,但使用锁和解锁。这与 pImpl 模式非常相似,只不过您的接口在调用实现之前添加了锁。这样用户只知道该对象是线程安全的,不需要担心同步。
Your design puts to onus on the user to ensure that the object is locked and unlocked at the correct times. And even though your locked object does error checking, it does not cover all bases (like forgetting to release the object when finished with it)
So lets say you have Unsafe object
TData
. You wrap this inFoo
but instead ofFoo
returning a pointer toTData
, reimplement all the public methods inTData
inFoo
but using locks and unlocks.This is very similar to the pImpl pattern except your interface adds locks before calling the implementation. This way the user just knows the object is thread safe and does not need to worry about synchronization.
这是多线程的核心问题,你不能要求客户端代码以线程安全的方式使用你的对象。你也不能真正做任何事情来帮助客户端代码落入成功的陷阱,它必须自己处理锁定。将让代码正常运行的责任交给最不可能正确执行的人。
您可以通过从访问器返回对象的副本来使其变得更容易。这是线程安全的,一个线程只会拥有一份副本。您可能应该使该副本的类型不可变,以强化修改对象不太可能获得所需的结果。一个无法解决的、可能会造成严重后果的副作用是,这个副本本质上是过时的。这些都是弊大于利的创可贴。
记录方法的填充,以便客户端程序员知道该怎么做。
This is the core problem of multi-threading, you cannot demand that the client code uses your object in a thread-safe manner. Nor can you really do anything to help the client code fall into the pit of success, it has to take care of the locking by itself. Putting the onus of making your code work on the person that is least likely to get it right.
You can make it easier by returning a copy of the object from your accessor. That's thread-safe, there will only be one copy owned by one thread. You probably ought to make that copy's type immutable to re-inforce that modifying the object won't likely have the desired outcome. An unsolvable side-effect that might bite badly is that this copy is by definition stale. These are band-aids that are likely to do more harm than good.
Document the stuffing out of the method so that the client programmer knows what to do.
这不是特别安全。没有什么可以阻止用户把事情弄乱:
当然,很容易看出为什么上面的代码很糟糕,但真正的代码要复杂得多,并且有很多方法可以使指针在锁释放后悬停。更难确定。
This isn't particularly safe. Nothing stops the user from getting things out of order:
Of course, it's easy to see why the above code is bad, but real code is much more complex, and there are many ways in which pointers could hang around after the lock is released that are much more difficult to pin down.