我正在查看 Qt 4.8.0 对 QString
隐式共享的实现,特别是它是如何以线程安全的方式完成的。似乎关键思想是将引用计数保存在 QBasicAtomicInt
类型的整数 d->ref
中,可以自动递增和递减。供引用:
inline QString::QString(const QString &other) : d(other.d)
{
Q_ASSERT(&other != this);
d->ref.ref();
}
inline QString::~QString()
{
if (!d->ref.deref())
free(d);
}
inline bool QBasicAtomicInt::deref()
{
unsigned char ret;
asm volatile("lock\n"
"decl %0\n"
"setne %1"
: "=m" (_q_value), "=qm" (ret)
: "m" (_q_value)
: "memory");
return ret != 0;
}
假设QString
对象A中d->ref
的当前值为1,调用A.deref()
。一旦确定了 ret != 0
表达式(即 false
)的值,是否有可能不同的执行线程可以复制 QString
,因此将其内部引用递增到 1?例如,如果第二个线程有一个指向 A 的指针,然后执行类似 QString otherString = *Aptr;
的操作,就会发生这种情况,这将调用复制构造函数。在这种情况下,看到 deref()
返回 false
的线程将释放共享内存,但第二个线程会认为它仍然有效。
我错过了什么?是不是一旦你进入多线程,将指针指向这些类型的对象本质上就容易出错并且应该避免?只要您只使用类线程的接口(interface)并且不使用指向它们的指针,类线程是否安全?
最佳答案
Isn't it possible that once the value of the ret != 0 expression (i.e. false) has been decided a different thread of execution could copy the QString, therefore incrementing its internal reference to 1?
不,因为如果 d->ref.deref() 返回 false,那么我们可以保证没有其他指向 d 的指针,因此任何其他线程都无法调用 ref()(或其他任何东西)在那个物体上。
或者,换句话说,如果在某处有另一个 QString 对象持有指向同一个共享数据对象 (d) 的指针,那么 deref() 一开始就不会返回 false ,因为 (d) 的引用计数仍会大于零。
What am I missing? Is the class thread safe as long as you only use its interface and refrain from taking pointers to them?
只要每个 QString 对象一次只能由一个线程访问,QString 类就是线程安全的——也就是说,您的代码可以处理 QString 对象,就好像它们没有执行任何共享数据一样技巧;共享数据技巧对调用代码是透明的(除了你的程序使用的内存比它本来拥有的更少的事实之外,当然 :) )
Is it that once you go multi-threaded taking pointers to these types of objects is inherently error-prone and should be avoided?
让两个线程同时尝试访问同一个 QString 对象 (A) 确实是不安全的,就像即使 QString 类没有进行任何隐式数据共享一样。所以这是一个禁忌(除非您使用 QMutex 或其他一些序列化机制显式序列化访问)。你应该做的是给每个线程它自己单独的 QString 对象(不要担心增加你的内存使用,因为隐式共享技巧可以避免这种情况)。
关于c++ - QString 的隐式共享实现如何线程安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35397279/