所谓原子操作,即一系列复杂的操作能一气呵成,中间不被其他的操作打断。这在多线程程序中尤其常见,但要实现这种功能,既要考虑程序的良好设计,又要关心特定平台的体系结构和相关编译器对原子特性的支持程度。所以,为了简化这个过程,Qt为我们提供了QAtomicInteger模板类,该类封装了大量与原子操作相关的细节和底层特性,为我们提供了方便易用的上层接口。虽然,该类并不能解决所有的原子操作问题,比如在不同的内存模型下,怎么保证对共享变量的原子访问,还需我们人为的控制,但它已经大大减轻了我们的工作复杂度。
QAtomicInteger模板类主要为我们提供了整数常用的原子操作,如 reference counting、test-and-set、fetch-and-store、fetch-and-add。QAtomicInteger,顾名思义,该类只能应用于整数类型,那么我们下来看下在Qt中哪些整型能使用该类进行原子操作。如下表:
位数 类型
8-bit char, signed char, unsigned char, qint8, quint8
16-bit short, unsigned short, qint16, quint16, char16_t(c++11)
32-bit int, unsigned int, qint32, quint32, char32_t(c++11)
64-bit long long, unsigned long long, qint64, quint64
platform-specific size long, unsigned long
pointer size qintptr, quintptr, qptrdiff
在上表中,只有32-bit 和 pointer-sized 的实例在所有的平台上都能得到保证。但对其他大小的支持需要看特定的编译器和运行改程序的处理器。为了测试自己的平台是否支持某种类型,可以使用Qt提供的宏Q_ATOMIC_INT{nn}_IS_SUPPORTED,这里的nn就是你想测试的类型的位数。
下面,在具体看QAtomicInteger提供的操作之前,我们先来看一下与原子操作有关的内存顺序(内存模型)。
刚才我们说到QAtomicInteger为我们提供了几种原子操作 test-and-set、fetch-and-store、fetch-and-add。其实这些函数的实现都定义了一种内存顺序的语义,这个语义描述了当处理器执行原子语句时怎么访问这些原子语句及其 前后的内存。因为当代的处理器架构允许对内存进行随意的访问,所以,为了让程序在所以的处理器上都能正确执行,使用一种合适的内存访问语义是至关重要的。在Qt中,为我们提供了4中内存模型:
Relaxed - 即不具体指定内存访问的顺序,编译器和处理器可以自由的对内存访问进行重新排序。
Acquire - 原子操作之后的内存访问(已程序的顺序)不会在原子操作之前被重新排序。
Release - 原子操作之前的内存访问(已程序的顺序)不会在原子操作之后被重新排序。
Ordered - Acquire 和 Release 的组合。
接下来,我们具体看下相关的原子操作API:
Reference counting
函数ref() 和 deref() 提供了高效的引用计数API。这些函数的返回值表明了什么时候最后一个引用被是释放了。这些功能可以用来实现我们自己的隐式共享类。如下代码所示:
MySharedType &MySharedType::operator=(const MySharedType &other)
{
(void) other.data->atomicInt.ref();
if (!data->atomicInt.deref()) {
// The last reference has been released
delete d;
}
d = other.d;
return *this;
}
Test-and-set
这些函数完成的功能是如果QAtomicInteger的当前值等于我们传入的期望值,则test-and-set函数会为其赋一个新值,然后返回true。如果当前值不等于传入的期望值,则这些函数声明也不干,直接返回false。即等价于一下的代码逻辑:
if (currentValue == expectedValue) {
currentValue = newValue;
return true;
}
return false;
在QAtomicInteger中为我们提供了4个test-and-set函数,分别是:testAndSetRelaxed()、testAndSetAcquire()、testAndSetRelease()、testAndSetOrdered()。其实就是以不同的内存模型进行操作。
Fetch-and-store
fetch-and-store 函数的功能是读取QAtomicInteger对象的当前值,并且为它设置一个我们传入的新值,然后返回读取到的旧值。该操作等同与以下的代码逻辑:
int originalValue = currentValue;
currentValue = newValue;
return originalValue;
在QAtomicInteger中有4个fetch-and-store函数:fetchAndStoreRelaxed()、fetchAndStoreAcquire()、fetchAndStoreRelease()、fetchAndStoreOrdered()。
Fetch-and-add
fetch-and-add函数读取QAtomicInteger对象的当前值,然后为它加上我们传入的值,最后返回原来的值。其执行逻辑类似于下面的代码:
int originalValue = currentValue;
currentValue += valueToAdd;
return originalValue;
在QAtomicInteger中有4个fetch-and-add方法: fetchAndAddRelaxed()、fetchAndAddAcquire()、fetchAndAddRelease()、fetchAndAddOrdered()。
特性测试相关的API
提供一个平台无关的、能应用于所以处理器的原子操作API是有挑战性的。所以,QAtomicInteger类提供的API能保证在所有的处理器的完成原子操作,但是,并不是所以的处理器都支持QAtomicInteger所提供的这些操作。所以,在使用这些操作之前,检测一下当前处理器是否支持某个API是很重要的。
所以Qt提供了大量的宏,你可以使用这些宏在编译器就可以检测你的硬件是否支持某个特性。这些宏会告诉你你的硬件是 支持该操作、有时支持该操作、不支持该操作。并且,这些宏有大致相同的形式,方便记忆,类似于 Q_ATOMIC_INTnn_OPERATION_IS_HOW_NATIVE。其中,nn是你要测试的整形的位数,operation是REFERENCE_COUNTING、TEST_AND_SET、FETCH_AND_STORE、FETCH_AND_ADD 其中之一,how是ALWAYS、SOMETIMES、NOT其中之一。并且,对应每种组合只有一个确定的宏。例如,如果Q_ATOMIC_INT32_REFERENCE_COUNTING_IS_ALWAYS_NATIVE 被定义了,那么Q_ATOMIC_INT_REFERENCE_COUNTING_IS_SOMETIMES_NATIVE 和 Q_ATOMIC_INT32_REFERENCE_COUNTING_IS_NOT_NATIVE 都不会被定义。
如果一个操作能在常量时间内完成,我们说它是wait-free。这类操作的实现不需要用到锁或者某种循环。并且,在Qt中,被平台一直支持的原子操作都是wait-free的。另外,Qt还定义了宏Q_ATOMIC_INTnn_OPERATION_IS_WAIT_FREE 来检测一个原子操作是否是wait-free的。
除此之外,有些原子操作只能在较新的处理器上呗支持,所以,我们除了需要在编译时检测某个特性是否被支持外,在程序运行时也需要这种检测。所以,Qt除了提供上面的宏用于编译时检测,也提供了几个API用户在代码中进行运行时的检测,如 isReferenceCountingNative()、isTestAndSetNative()、isFetchAndStoreNative()、isFetchAndAddNative()。同时,也提供了对wait-free特性的检测函数,如isReferenceCountingWaitFree()、isTestAndSetWaitFree()、isFetchAndStoreWaitFree()、isFetchAndAddWaitFree()。
Qt中的原子操作在不同的版本中是不同的,所以Qt处于对老版本的兼容性,规定不带nn的宏就等价于32-bit的宏。例如,Q_ATOMIC_INT_REFERENCE_COUNTING_IS_WAIT_FREE 等价于 Q_ATOMIC_INT32_REFERENCE_COUNTING_IS_WAIT_FREE。
最后,上面只是从大的方面讲解了QAtomicInteger类的功能,至于其中的每个函数的具体使用也都是见名知意的,比如 ++、--、load、store等,在此就不一一讲解了,大家可以在用到时,参考Qt帮助文档即可。另外,Qt还提供了QAtomicInt 和 QAtomicPointer 类,用法与此类似。
---------------------
作者:求道玉
来源:CSDN
原文:https://blog.csdn.net/Amnes1a/article/details/62881888
版权声明:本文为博主原创文章,转载请附上博文链接!