您的位置:首页 > 其它

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

2017-11-22 17:59 399 查看

何为线程安全?

在多线程环境中编程,大家一直都强调线程安全,可以什么是线程安全呢?

依据《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 <iostream>
#include <pthread.h>

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 <thread>
#include <mutex>
#include <iostream>

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中的则未作处理。 )
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程安全
相关文章推荐