我正在查看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("lockn"
"decl %0n"
"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
的线程将释放共享内存,但第二个线程会认为它仍然有效。
我错过了什么?是不是一旦你进入多线程,就要指针指向这些类型的对象,本质上很容易出错,应该避免?只要您只使用其接口并避免指向它们的指针,类线程是否安全?
难道一旦 ret != 0 表达式的值(即 false(已决定不同的执行线程可以复制 Qstring,因此将其内部引用增加到 1?
不,因为如果 d->ref.deref(( 返回 false,那么我们保证没有其他指向 d 的指针,因此任何其他线程都无法在该对象上调用 ref(((或其他任何东西(。
或者,换句话说,如果某个地方有另一个QString对象持有指向同一共享数据对象(d(的指针,那么deref((首先不会返回false,因为(d(的引用计数仍然大于零。
我错过了什么?只要您只使用 类线程是否安全 它的界面并避免指向它们?
只要每个 QString 对象一次只能由单个线程访问,QString 类就是线程安全的——也就是说,你的代码可以处理 QString 对象,就像它们没有做任何共享数据技巧一样;共享数据技巧对调用代码是透明的(除了程序使用的内存比其他情况下少的事实之外(。 当然:)(
是不是一旦你去多线程采取 指向这些类型的对象的指针本质上容易出错,并且 应该避免吗?
让两个线程同时尝试访问同一个 QString 对象 (A( 确实是不安全的,就像即使 QString 类不进行任何隐式数据共享也是如此一样。 所以这是一个禁忌(除非你使用 QMutex 或其他一些序列化机制显式序列化访问(。 相反,您应该做的是为每个线程提供自己单独的 QString 对象(并且不必担心增加内存使用量,因为隐式共享技巧可以避免这种情况(。