C++:并发和析构函数
假设您有一个可以被许多线程访问的对象。关键部分用于保护敏感区域。但是析构函数呢?即使我一进入析构函数就进入临界区,一旦析构函数被调用,对象是否就已经失效了?
我的思路:假设我进入析构函数,我必须等待临界区,因为其他线程仍在使用它。一旦他完成了,我就可以完成对该物体的销毁。这有道理吗?
Suppose you have an object which can be accesed by many threads. A critical section is used to protect the sensitive areas. But what about the destructor? Even if I enter a critical section as soon as I enter the destructor, once the destructor has been called, is the object already invalidated?
My train of thought: Say I enter the destructor, and I have to wait on the critical section because some other thread is still using it. Once he is done, I can finish destroying the object. Does this make sense?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
一般来说,在知道没有其他线程正在使用某个对象之前,不应销毁该对象。时期。
根据您的“思路”考虑以下场景:
现在考虑如果时间稍有不同会发生什么:
简而言之,对象销毁必须在对象本身以外的其他地方同步。一种常见的选择是使用引用计数。线程 A 将锁定对象引用本身,防止引用被删除和对象被销毁,直到它设法增加引用计数(保持对象处于活动状态)。然后线程 B 只是清除引用并减少引用计数。您无法预测哪个线程将实际调用析构函数,但无论哪种方式都是安全的。
使用
boost 可以轻松实现引用计数模型: :shared_ptr
或std::shared_ptr
;除非所有线程中的所有shared_ptr
都已被销毁(或指向其他地方),否则析构函数不会运行,因此在销毁时您知道指向剩余对象的唯一指针是this
析构函数本身的指针。请注意,使用shared_ptr时,重要的是要防止原始对象引用发生更改,直到您可以捕获它的副本。例如:
请注意,在
shared_ptr
上同时读取是安全的,因此如果对您的应用程序有意义,您可以选择使用读写锁。In general, you should not destroy an object until you know that no other thread is using it. Period.
Consider this scenario, based on your 'train of thought':
Now consider what happens if the timing is slightly different:
In short, object destruction must be synchronized somewhere other than the object itself. One common option is to use reference counting. Thread A will take a lock on the object reference itself, preventing the reference from being removed and the object being destroyed, until it manages to increment the reference count (keeping the object alive). Then thread B merely clears the reference and decrements the reference count. You can't predict which thread will actually call the destructor, but it will be safe either way.
The reference counting model can be implemented easily by using
boost::shared_ptr
orstd::shared_ptr
; the destructor will not run unless allshared_ptr
s in all threads have been destroyed (or made to point elsewhere), so at the moment of destruction you know that the only pointer to the object remaining is thethis
pointer of the destructor itself.Note that when using shared_ptr, it's important to prevent the original object reference from changing until you can capture a copy of it. Eg:
Note that simultaneous reads on a
shared_ptr
is safe, so you can choose to use a read-write lock if it makes sense for your application.如果一个对象正在使用中,那么您应该确保在该对象的使用结束之前不会调用该对象的析构函数。如果这是您的行为,那么它是一个潜在的问题,确实需要解决。
您应该确保,如果一个线程正在销毁您的对象,那么另一个线程不应该在该对象上调用函数,或者第一个线程应该等到第二个线程完成函数调用。
是的,即使析构函数也可能需要关键部分来保护更新一些与类本身无关的全局数据。
If a object is in use then you should make sure that the destructor of the object is not being called before the use of the object ends. If this is the behavior you have then its a potential problem and it really needs to be fixed.
You should make sure that if one thread is destroying your objects then another thread should not be calling functions on that object or the first thread should wait till second thread completes the function calling.
Yes, even destructors might need critical sections to protect updating some global data which is not related to the class itself.
有可能当一个线程在析构函数中等待 CS 时,另一个线程正在销毁对象,如果 CS 属于对象,它也会被销毁。所以这不是一个好的设计。
It's possible that while one thread is waiting for CS in destructor the other is destroying the object and if CS belongs to object it will be destroyed as well. So that's not a good design.
您绝对需要确保对象的生命周期短于消费者线程,否则您将遇到一些严重的麻烦。要么:
如果您选择后一条路线,我强烈推荐 0mq http://www.zeromq.org/。
You absolutely, positively need to make sure your object lifetime is less than the consumer threads, or you are in for some serious headaches. Either:
If you go the latter route, I highly recommend 0mq http://www.zeromq.org/.
是的,当您处于析构函数中时,该对象已经失效。
我使用 Destroy() 方法进入临界区,然后自行销毁。
在调用析构函数之前对象的生命周期就结束了?
Yes while you are in destructor, the object is already invalidated.
I used Destroy() method that enters critical section and then destroys it self.
Lifetime of object is over before destructor is called?
是的,这样做很好。 如果一个类支持这样的使用,客户端不需要同步销毁;即他们不需要确保对象上的所有其他方法在调用析构函数之前已完成。
我建议客户不要假设他们可以做到这一点,除非有明确的记录。默认情况下,客户端确实有这种负担,尤其是标准库对象(§17.6.4.10/2)。
但也有一些情况是没问题的;例如,
std::condition_variable
的析构函数专门允许在~condition_variable()
启动时持续调用condition_variable::wait()
方法。它只要求客户端在~condition_variable()
启动后不要发起对 wait() 的调用。要求客户端同步对析构函数和构造函数的访问可能会更清晰,就像大多数标准库的其余部分所做的那样。如果可行的话我建议这样做。
然而,在某些模式下,减轻客户完全同步销毁的负担可能是有意义的。
condition_variable
的总体模式看起来像这样:考虑使用处理可能长时间运行的请求的对象。用户执行以下操作:另一种方法是要求客户端确实需要同步访问。您可以想象在上面的步骤 3.5 中,客户端对执行阻塞的对象调用
shutdown()
方法,之后客户端就可以安全地销毁该对象。然而,这种设计有一些缺点;它使 API 变得复杂,并为 shutdown-but-valid 对象引入了一个附加状态。相反,可以考虑阻塞步骤 (3),直到所有请求都完成。有一些权衡...
Yes, it is fine to do that. If a class supports such use, clients don't need to synchronize destruction; i.e. they don't need to make sure that all other methods on the object have finished before invoking the destructor.
I would recommend that clients not assume they can do this unless it is explicitly documented. Clients do have this burden, by default, with standard library objects in particular(§17.6.4.10/2).
There are cases where it is fine, though;
std::condition_variable
's destructor, for example, specifically allows ongoingcondition_variable::wait()
method invocations when~condition_variable()
starts. It only requires that clients not initiate calls to wait() after~condition_variable()
starts.It might be cleaner to require that the client synchronize access to the destructor – and constructor for that matter – like most of the rest of the standard library does. I would recommend doing that if feasible.
However, there are certain patterns where it might make sense to relieve clients of the burden of fully synchronizing destruction.
condition_variable
's overall pattern seems like one: consider use of an object that handles possibly long-running requests. The user does the following:An alternative would be to require that clients do need to synchronize access. You could imagine step 3.5 above where the client calls a
shutdown()
method on the object that does the blocking, after which it is safe for the client to destroy the object. However, this design has some downsides; it complicates the API and introduces an additional state for the object of shutdown-but-valid.Consider instead perhaps getting step (3) to block until all requests are done. There are tradeoffs...