您的位置:首页 > 其它

Juce源码分析(三)数据的原子操作

2014-10-27 09:23 405 查看
在多线程程序中,当多个线程访问同一个共享变量时,我们会采取多种办法来避免共享变量被多个线程同时访问,因为大家都知道C语言的一个操作符(比如+、-、*、/)可能会对应N条汇编指令,当程序指针EIP进入一个操作符中,还没有执行完这个操作符,该操作符访问的是共享变量,这时CPU突然切换到其他线程,另一个线程也去操作这个共享变量,当CPU再重新跳回上次的线程时会发现操作符没执行完的另一半已经和上次的接不上了,因为共享变量的值已经被改变。这就好比说,我想算(2+3)*4等于多少,当我算出2+3=5时,突然想上厕所,于是我不得不先把结果“5”先记在一张纸上,这张纸好比是共享变量,恰好在我上厕所时,又有人使用这张纸,把“6”记在了这张纸上,等我回来后会用纸上的数字“6”去乘以4得到了错误结果24。所以说,共享的东西,一定要一个一个来,千万不要用到一半再去让别人用,否则很容易造成误解,然而,有时候这种误解是致命的。我再举一个更贴切的例子来说明这个问题的严重性,A村有两个电工,小张和小王,有一天村里的线路出故障了,村长派小张过去修理,就是小张就把变压器的总闸拉了下来,开始修理线路,这时村长等的不耐烦了,说怎么还没来电又派小王去看看,小王屁颠屁颠就去了变压器那里,打开闸箱子后发现总闸是断开的,怪不得没电呢,哪个兔崽子搞的鬼?于是三下五除二把电闸合上了,后果也就不用说了,这事怪谁?怪就怪小张自己,维修的时候没有把闸箱子锁上。

再回到我们的编程角度,不要认为区区几行代码出不了啥事,倘若你的代码控制的是原子弹,不把你自己炸死才怪呢。所以,为了保证的程序的健壮性,在多线程操作共享变量时,我们也要加一把锁,等操作完再解锁。那么,这个锁到底怎么来加?对于单个操作符,windows系统已经为我们提供了带锁的操作符,比如_InterlockedIncrement_InterlockedDecrement_InterlockedExchangeAdd等等,这些就叫做原子操作符,当在多线程中对共享变量进行操作时,我们就使用这些原子操作符来代替普通操作符,就可以避免上述问题了,比如我们先对共享变量count进行自加操作,在正常情况下我们会使用count++,在多线程中则会用_InterlockedIncrement来代替。

原子操作符虽然能解决多线程操作共享变量的问题,但是随着程序的复杂性提高,原子操作符的使用就会增多,这会使代码的可读性非常差。原本就是+,-,=这些简单操作,变成一堆_Interlocked开头的函数,这谁看了不头疼?这时操作符重载表现的机会来了,重载普通操作符为原子操作符,这个方法太不错了,既解决了多线程的问题,又使代码变得非常简单易懂。

下面我们看一下Juce是怎么做的,打开juce_core下面memory里面的juce_Atomic.h文件,我们看到了一个Atomic类,这就是我们今天要说的“原子数据类型”

template <typename Type>
class Atomic
{
public:
/** Creates a new value, initialised to zero.
创建一个原子变量
*/
inline Atomic() noexcept
: value (0)
{
}

/** Creates a new value, with a given initial value.
创建一个原子变量,并初始化它的值
*/
inline explicit Atomic (const Type initialValue) noexcept
: value (initialValue)
{
}

/** Copies another value (atomically).
复制构造函数,构造一个与参数一样的对象
*/
inline Atomic (const Atomic& other) noexcept
: value (other.get())
{
}

/** Destructor.
析构函数
*/
inline ~Atomic() noexcept
{
// This class can only be used for types which are 32 or 64 bits in size.
static_jassert (sizeof (Type) == 4 || sizeof (Type) == 8);
}

/** Atomically reads and returns the current value.
取出变量真正的值
*/
Type get() const noexcept;

/** Copies another value onto this one (atomically).
原子数据赋值
*/
inline Atomic& operator= (const Atomic& other) noexcept         { exchange (other.get()); return *this; }

/** Copies another value onto this one (atomically).
普通数据赋值
*/
inline Atomic& operator= (const Type newValue) noexcept         { exchange (newValue); return *this; }

/** Atomically sets the current value.
修改原子变量的值
*/
void set (Type newValue) noexcept                               { exchange (newValue); }

/** Atomically sets the current value, returning the value that was replaced.
修改原子变量的值
*/
Type exchange (Type value) noexcept;

/** Atomically adds a number to this value, returning the new value.
重载+=操作符,使用原子操作符代替
*/
Type operator+= (Type amountToAdd) noexcept;

/** Atomically subtracts a number from this value, returning the new value. */
Type operator-= (Type amountToSubtract) noexcept;

/** Atomically increments this value, returning the new value. */
Type operator++() noexcept;

/** Atomically decrements this value, returning the new value. */
Type operator--() noexcept;

/** Atomically compares this value with a target value, and if it is equal, sets
this to be equal to a new value.

This operation is the atomic equivalent of doing this:
@code
bool compareAndSetBool (Type newValue, Type valueToCompare)
{
if (get() == valueToCompare)
{
set (newValue);
return true;
}

return false;
}
@endcode

@returns true if the comparison was true and the value was replaced; false if
the comparison failed and the value was left unchanged.
@see compareAndSetValue
*/
bool compareAndSetBool (Type newValue, Type valueToCompare) noexcept;

/** Atomically compares this value with a target value, and if it is equal, sets
this to be equal to a new value.

This operation is the atomic equivalent of doing this:
@code
Type compareAndSetValue (Type newValue, Type valueToCompare)
{
Type oldValue = get();
if (oldValue == valueToCompare)
set (newValue);

return oldValue;
}
@endcode

@returns the old value before it was changed.
@see compareAndSetBool
*/
Type compareAndSetValue (Type newValue, Type valueToCompare) noexcept;

/** Implements a memory read/write barrier. */
static void memoryBarrier() noexcept;

//==============================================================================
#if JUCE_64BIT
JUCE_ALIGN (8)
#else
JUCE_ALIGN (4)
#endif

/** The raw value that this class operates on.
This is exposed publically in case you need to manipulate it directly
for performance reasons.
*/
volatile Type value;

private:
template <typename Dest, typename Source>
static inline Dest castTo (Source value) noexcept         { union { Dest d; Source s; } u; u.s = value; return u.d; }

static inline Type castFrom32Bit (int32 value) noexcept   { return castTo <Type, int32> (value); }
static inline Type castFrom64Bit (int64 value) noexcept   { return castTo <Type, int64> (value); }
static inline int32 castTo32Bit (Type value) noexcept     { return castTo <int32, Type> (value); }
static inline int64 castTo64Bit (Type value) noexcept     { return castTo <int64, Type> (value); }

Type operator++ (int); // better to just use pre-increment with atomics..
Type operator-- (int);

/** This templated negate function will negate pointers as well as integers */
template <typename ValueType>
inline ValueType negateValue (ValueType n) noexcept
{
return sizeof (ValueType) == 1 ? (ValueType) -(signed char) n
: (sizeof (ValueType) == 2 ? (ValueType) -(short) n
: (sizeof (ValueType) == 4 ? (ValueType) -(int) n
: ((ValueType) -(int64) n)));
}

/** This templated negate function will negate pointers as well as integers
取负值
*/
template <typename PointerType>
inline PointerType* negateValue (PointerType* n) noexcept
{
return reinterpret_cast <PointerType*> (-reinterpret_cast <pointer_sized_int> (n));
}
};


我们可以这样使用

Atomic<int> a; //一个共享变量

这时我们就可以在多线程中放心地使用a++,a--或者a+=5,这些都没有问题了

但是有一点,Atomic对所维护的数据类型有要求,一定要是4位或8位的,比方说int ,__int64,或者任意的指针类型,void*,char*等

Atomic<char> a; //这样是不允许的

Atomic<char*> a; //但这样是可以的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: