QString的隐式共享实现如何保证线程安全?

3

我正在查看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;
}

假设在一个名为A的QString对象中,d->ref的当前值为1,并且调用了A.deref()。有没有可能一旦ret != 0表达式(即false)的值被确定后,另一个执行线程就会复制该QString,从而将其内部引用增加1?例如,如果第二个线程有一个指向A的指针,然后执行了QString otherString = *Aptr;, 这将调用复制构造函数。在这种情况下,看到deref()返回false的线程将释放共享内存,但第二个线程将认为它仍然有效。

我错过了什么?是不是一旦使用多线程,获取这些类型对象的指针就本质上容易出错,应该避免使用?只要使用其接口并避免引用它们,该类就是线程安全的吗?


由于deref是被析构函数调用的,那么从悬空指针中调用将导致未定义行为。但我不知道在不同的上下文中是否会调用deref - Weak to Enuma Elish
1个回答

1

当 ret != 0 表达式的值(即 false)被确定后,是否有可能另一个执行线程会复制 QString,从而将其内部引用增加到 1?

不可能,因为如果 d->ref.deref() 返回 false,则我们保证没有其他指针指向 d,因此没有其他线程可以在该对象上调用 ref()(或其他任何东西)。

换句话说,如果还有另一个 QString 对象持有指向相同共享数据对象(d)的指针,那么 deref() 在第一次调用时就不会返回 false,因为(d)的引用计数仍大于零。

我错过了什么?只要您仅使用其接口并避免对它们进行指针操作,该类就是线程安全的吗?

只要每个 QString 对象仅由单个线程访问,QString 类就是线程安全的 - 也就是说,您的代码可以像处理没有共享数据技巧的对象一样处理 QString 对象;共享数据技巧对调用代码是透明的(当然,除了您的程序使用的内存比本来应该使用的更少之外 :))。

一旦您使用多线程,获取这些类型对象的指针就具有内在的错误风险,应该避免吗?

两个线程同时尝试访问同一个QString对象(A)将是不安全的,即使QString类不执行任何隐式数据共享也是如此。因此,这是不可行的(除非您明确地使用QMutex或其他序列化机制串行访问)。相反,您应该为每个线程提供其自己的单独的QString对象(不要担心增加内存使用量,因为隐式共享技巧可以避免这种情况)。


没有另一个QString对象,但有一个指向它的QString *,并且它在适当的时候被取消引用(QString callCopyConstructor = *Aptr),以便增加QBasicAtomicInt。 - MuchToLearn
确实。在没有任何同步的情况下从两个不同的线程访问单个QString对象是经典的竞态条件,并且会导致未定义的行为。请参见我答案中的最后一段。 - Jeremy Friesner
@JeremyFriesner 为了让我们所有的读者都明白:你是说即使线程所拥有的只是对 QString 的常量只读访问,并且它们中没有一个在修改它,那么仍然可能会导致崩溃,因此需要同步来防止同时读取。我说得对吗? - Patrick Parker
1
如果在多个线程可以引用QString的期间内,QString从未被修改或删除,则它实际上是一个只读数据结构,并且可以在没有同步的情况下安全地访问。在我的帖子中,我假设QString在可访问于多个线程的某个时刻将被修改(或可能被修改);也许我应该明确说明这一点。 - Jeremy Friesner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接