我正在查看Qt 4.8.0对QString
隐式共享的实现,特别是它是如何以线程安全的方式完成的。似乎关键的想法是将引用计数保存在d->ref
类型的整数QBasicAtomicInt
中,该整数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;
}
可以原子递增和递减。对于参考:
d->ref
假设QString
对象A中A.deref()
的当前值为1且调用ret != 0
。一旦确定了false
表达式的值(即QString
),不同的执行线程就可以复制QString otherString = *Aptr;
,从而增加其内部引用的可能性。 1?例如,如果第二个线程有一个指向A的指针然后执行类似deref()
的操作,则会发生这种情况,这会调用复制构造函数。在那种情况下,看到false
返回x match {
case ThingOne => println("thing one")
case ThingTwo => println("thing two")
case x => println("unknown: " + x)
}
的线程将释放共享内存,但第二个线程会认为它仍然有效。
我错过了什么?是不是一旦你多线程指向这些类型的对象本身就容易出错,应该避免?只要你只使用它的接口并避免指向它们,那么类线程是否安全?
答案 0 :(得分:0)
一旦ret!= 0表达式的值(即 false)已经决定了一个不同的执行线程可以复制 QString,因此将其内部引用递增为1?
不,因为如果d-> ref.deref()返回false,那么我们可以保证没有其他指针指向d,因此任何其他线程都无法调用ref()(或其他任何东西) )关于那个对象。
或者,换句话说,如果有另一个QString对象在那里拿着指向同一共享数据对象的指针(d),那么deref()就不会在第一时间返回false ,因为(d)的引用计数仍然大于零。
我错过了什么?只要您使用,类线程是否安全 它的界面,不要指点它们?
QString类是线程安全的,只要每个QString对象一次只能由一个线程访问 - 也就是说,您的代码可以处理QString对象,就像它们没有执行任何共享数据一样技巧;共享数据技巧对调用代码是透明的(当然,除了你的程序使用的内存少于原本应该使用的内存之外:)当然:))
一旦你进行多线程服用就是这样吗? 指向这些类型对象的指针本质上容易出错 应该避免?
两个线程同时尝试访问同一个QString对象(A)确实是不安全的,就像QString类没有进行任何隐式数据共享一样。所以这是禁忌(除非你使用QMutex或其他一些序列化机制明确地序列化访问)。你应该做的是给每个线程自己独立的QString对象(不要担心增加你的内存使用量,因为隐式共享技巧可以避免这种情况)。