如何编写一个线程安全的程序或者函数库?

何为线程安全?

在多线程环境中编程,大家一直都强调线程安全,可以什么是线程安全呢?
依据《Java 并发编程实践》/《Java Concurrency in Practice》一书,一个线程安全的Class
应当满足三个条件:

  • 从多个线程访问时,其表现出正确的行为
  • 无论操作系统如何调度这些线程,无论这些线程的执行顺序如何交织
  • 调用端代码无需额外的同步或其他协调动作

具体说来,线程安全性体现在:
线程安全的函数通过“锁”来保护共享资源不被并发地访问。“线程安全”仅关心函数的实现,而不影响其外部接口。

如何实现线程安全?

实现线程安全的主要方法就是确保多线程调用共享资源时能够正确,有序的调用。
可以对共享资源加互斥锁、原子操作等方法。
本文主要介绍如何用互斥锁确保共享资源调用的线程安全。

Mutex和MutexLock

首先介绍两个比较重要的工具类, 这两个工具类在C++11的标准库中都有实现。

Mutex

Mutex 是封装临界区,主要实现互斥器的创建和销毁。 临街区在Windows中是
CRITICAL_SECTION, 而在Linux上是pthead_mutex_t. 在C++11中的标准库中即是std::mutex

MutexLock/MutexGuard

MutexLock主要是负责临界区的进入和退出,也就是加锁和解锁。 在MutexLock执行构造函数时进行加锁操作,在执行析构函数时执行解锁操作,所以MutexLock的作用域就是自己的生命周期。 一般是函数内的局部变量。在C++11的实现是std::lock_guard

Mutex和MutexLock两个类都采用了RAII风格进行封装,这样封装的好处是保证了互斥锁出现死锁等现象。
具体的代码实现可以参考一下代码片。

#include 
#include 

using namespace std;

class MutexLock
{
public:
    MutexLock()
    {
        pthread_mutex_init(&mutex_, NULL);
    }

    ~MutexLock()
    {
        pthread_mutex_destroy(&mutex_);
    }

    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }

    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }

private:
    MutexLock(const MutexLock&);
    MutexLock& operator=(const MutexLock&);
    pthread_mutex_t mutex_;
};

class MutexLockGuard
{
public:
    explicit MutexLockGuard(MutexLock& mutex): mutex_(mutex)
    {
        mutex_.Lock();
    }

    ~MutexLockGuard()
    {
        mutex_.Unlock();
    }

private:
    MutexLockGuard(const MutexLockGuard&);
    MutexLockGuard& operator=(const MutexLockGuard&);

    MutexLock& mutex_;
};

class Foo
{
public:
    void print()
    {
        MutexLockGuard lock(mutex_);
        cout << "Hello" << endl;
    }

private:
    MutexLock mutex_;    
};

当然,如果利用C++11的特性,实现Class Foo 的线程安全,代码可以简化为:

#include 
#include 
#include 

class Foo
{
public:
    void print()
    {
        std::lock_guard<std::mutex> lock(p_mutex);
        cout << "Hello" << endl;
    }

private:
    std::mutex p_mutex;     
};

通过以上例子, 在一个函数内部调用MutexLock可以实现一个临街区,确保共享资源正常调用。

从Stackoverflow借用的答案可以具体阐述如何确保我们的程序是线程安全的。

  • 明确哪些资源是在多线程应用中被共享的
  • 创建一个Mutex, 并在任何你需要访问共享资源之前加锁。(这些共享资源最好是private to class, 否则你不确定是否全面的保护共享资源)
  • 清除掉全局变量。 最好不要用全局变量。
  • 注意static 关键字, 带有static 的函数是不可重入的, 如果想保证线程安全,需要加静态互斥锁。 (从这篇文章可以看出,不同的编译器似乎对static变量的处理不同,gcc中的static变量是线程安全的,VC中的则未作处理。 )

你可能感兴趣的:(linux)