C++ – 线程安全访问控制

加锁的原则

规则10.1 多线程 进程并行访问共享资源时 一定要加锁保护

共享资源包括全局变量,静态变量,共享内存,文件等。 建议封装像智能指针一样的对象对锁进行管理,比如我们就封装了一个 auto_lock ,在构造时申请锁,析构中释放锁,保证不会忘记解锁。

规则10.2 锁的职责单一

每个锁只锁一个唯一共享资源,这样,才能保证锁应用的单一,也能更好的确保加锁的范围尽 量小。对于共享全局资源,应该根据实际需要,每类或每个资源,有一把锁。这样,这把锁只锁对这个资源访问的代码,通常这样的代码都会是比较简单的资源操作代码,不会是复杂的函数调用等。相反,如 果我们对几类或几个资源共用一把锁。这把锁的责任范围就大了,使用复杂,很难理清锁之间的关系(有 没有释放锁,或者锁之间的嵌套加锁等),容易导致死锁问题。

规则10.3 锁范围尽量小 只锁对应资源操作代码

使用锁时,尽量减少锁的使用范围。我们使用锁,为了方便,会大范围的加锁,如:直接锁几个函数调用。这种使用,一方面会导致多线程执行效率的低下,容易变成串行执行;另一方面,容易出现锁未释放,或者锁的代码中再加锁的场景,最后导致死锁。 所以,对锁操作的最好办法,就是只锁简单资源操作代码。对应资源访问完后,马上释放锁。尽量在 函数内部靠近资源操作的地方加锁而不是靠近线程、函数外部加锁。

规则10.4 避免嵌套加锁 如果必须加锁 务必保证不同地方的加锁顺序是一样的

加上一把锁之后,在释放之前,不能再加锁。 典型的锁中加锁的场景: OMU 代码中对几个容器的同时遍历,每个容器一把锁,就导致需要加多把锁。 这种场景的解决方法:先加一把锁,对一个容器遍历,选择出合乎要求的数据,并保存在临时变量中; 再加另一把锁,使用临时变量,再对其他容器遍历。 锁中加锁。必须保证加锁的顺序是一样的,比如先加的锁后解锁, Lock1 Lock2 Unlock2 Unlock1 ,则其他地方的加锁顺序,必须与这里的顺序一样,避免死锁,不允许出现: Lock2 Lock1 Unlock2 Unlock1 。

建议10.1 进程间通讯 使用自己保证互斥的系统

由于文件在不同进程间访问,无法保证互斥。当然,可以在进程间加进程锁,但只受限于我们能加锁的进程,对于第三方进程等无法保证。这样,当多个进程同时对文件进行写操作时,将会导致文件数据破坏,或文件写失败等问题。当数据库系统本身的访问接口带有互斥机制时,当多个进程同时访问时,可以保证数据库数据的完整。 共享内存,只限制于使用共享内存的几个进程,需要我们对这些访问共享内存的进程加锁。但由于共享内存,第三方进程等无法访问,这也能比较好的保护数据,避免文件系统存在的问题。 Socket 消息机制,由操作系统 Socket 通讯机制保证互斥,在多个进程间,通过消息来保证数据的互斥。 进程的消息都是操作系统转发而来的独立数据,属于进程私有数据,不存在进程间并行访问的问题。

建议10.2 可重入函数尽量只使用局部变量和函数参数 少用全局变量 静态变量

支持多线程并行访问的函数称之为可重入函数。设计可重入函数时,尽量使用局部变量和函数参数来传递数据,在多线程并行访问时,互相之间不会受影响。相反,如果使用全局变量、静态变量,就需要同步。一些库函数也是非线程安全,调用时可能会出现多线程并发访问问题。

建议10.3 锁中避免调用函数 如果必须调用函数 务必保证不会造成死锁

这条规则是对加锁范围尽量小(只锁对应资源操作代码)规则的补充。不能把调用函数也加到加锁范围中。因为被调用函数的内部到底做了什么事情,是如何做的,调用者可能不是很清楚。尤其是 当被调用函数内部又加锁的情况,就容易导致两个锁互饿,导致死锁。而且这种死锁 情况还是比较难分析。因为我们调用函数,很多时候只关注函数实现的功能 ,而忽略函数内部的具体实现。其次,锁中调用函数,也会把对资源操作的代码扩大化,不利于并行效率。更主要的是,这种操作, 由于加锁的范围变大,引起死锁的可能就增大。

建议10.4 锁中避免使用跳转语句

跳转语句包含 return、break、continue、goto 等。如果锁中有宏调用的代码,要特别注意,分析宏中是否存在隐含的跳转语句。 在函数返回时忘记把锁释放,特别是存在很多分支都可能返回的时候,可能一些分支会忘记释放锁。

通过封装器类确保加锁

每次当我们想要访问成员变量的时候,我们必须确保先获得了互斥锁。然而,这种模型容易出错。当我们在使用互斥量时忘记上锁的时候,编译器不会给我们发出警告。我们需要一种机制来确保同步访问这个成员变量,并且是在锁域内。这种机制基本是思想是使用两个类:一个作为访问控制器来自动给互斥量加锁及解锁,另一个则作为变量的封装类,

template<typename ObjectType> class TRFurLockedGuard;
template<typename ObjectType> class TRFurLockedWriter;
template<typename ObjectType> class TRFurLockedReader;

template<typename ObjectType>
class TRFurLockedGuard : FNoncopyable
{
public:

    FORCEINLINE TRFurLockedGuard();
    FORCEINLINE TRFurLockedGuard(const ObjectType& InObject);
    FORCEINLINE TRFurLockedGuard(ObjectType&& InObject);

    template<typename... ArgTypes>
    FORCEINLINE TRFurLockedGuard(ArgTypes&&... Args);

private:

    ObjectType Object;
    mutable FRWLock Mutex;

    friend TRFurLockedWriter<ObjectType>;
    friend TRFurLockedReader<ObjectType>;

};

template<typename ObjectType>
class TRFurLockedWriter : FNoncopyable
{
public:

    FORCEINLINE TRFurLockedWriter(TRFurLockedGuard<ObjectType>& InGuard);
    FORCEINLINE ~TRFurLockedWriter();

    FORCEINLINE ObjectType& Get();
    FORCEINLINE ObjectType& operator*();
    FORCEINLINE ObjectType* operator->();

private:  

    TRFurLockedGuard<ObjectType>& Guard;

};

template<typename ObjectType>
class TRFurLockedReader : FNoncopyable
{
public:

    FORCEINLINE TRFurLockedReader(const TRFurLockedGuard<ObjectType>& InGuard);
    FORCEINLINE ~TRFurLockedReader();

    FORCEINLINE const ObjectType& Get() const;
    FORCEINLINE const ObjectType& operator*() const;
    FORCEINLINE const ObjectType* operator->() const;

private:

    const TRFurLockedGuard<ObjectType>& Guard;

};

template<typename ObjectType>
FORCEINLINE TRFurLockedGuard<ObjectType>::TRFurLockedGuard()
    : Object()
{ }

template<typename ObjectType>
FORCEINLINE TRFurLockedGuard<ObjectType>::TRFurLockedGuard(const ObjectType& InObject)
    : Object(InObject)
{ }

template<typename ObjectType>
FORCEINLINE TRFurLockedGuard<ObjectType>::TRFurLockedGuard(ObjectType&& InObject)
    : Object(InObject)
{ }

template<typename ObjectType>
template<typename ...ArgTypes>
FORCEINLINE TRFurLockedGuard<ObjectType>::TRFurLockedGuard(ArgTypes && ...Args)
    : Object(Args...)
{ }

template<typename ObjectType>
FORCEINLINE TRFurLockedWriter<ObjectType>::TRFurLockedWriter(TRFurLockedGuard<ObjectType>& InGuard)
    : Guard(InGuard)
{
    Guard.Mutex.WriteLock();
}

template<typename ObjectType>
FORCEINLINE TRFurLockedWriter<ObjectType>::~TRFurLockedWriter()
{
    Guard.Mutex.WriteUnlock();
}

template<typename ObjectType>
FORCEINLINE ObjectType& TRFurLockedWriter<ObjectType>::Get()
{
    return Guard.Object;
}

template<typename ObjectType>
FORCEINLINE ObjectType& TRFurLockedWriter<ObjectType>::operator*()
{
    return Guard.Object;
}

template<typename ObjectType>
FORCEINLINE ObjectType* TRFurLockedWriter<ObjectType>::operator->()
{
    return &Guard.Object;
}

template<typename ObjectType>
FORCEINLINE TRFurLockedReader<ObjectType>::TRFurLockedReader(const TRFurLockedGuard<ObjectType>& InGuard)
    : Guard(InGuard)
{
    Guard.Mutex.ReadLock();
}

template<typename ObjectType>
FORCEINLINE TRFurLockedReader<ObjectType>::~TRFurLockedReader()
{
    Guard.Mutex.ReadUnlock();
}

template<typename ObjectType>
FORCEINLINE const ObjectType& TRFurLockedReader<ObjectType>::Get() const
{
    return Guard.Object;
}

template<typename ObjectType>
FORCEINLINE const ObjectType& TRFurLockedReader<ObjectType>::operator*() const
{
    return Guard.Object;
}

template<typename ObjectType>
FORCEINLINE const ObjectType* TRFurLockedReader<ObjectType>::operator->() const
{
    return &Guard.Object;
}

发表回复