可重入和线程安全简单介绍
可重入和线程安全
可重入和线程安全这两个术语,经常出现在计算机编程中,用于指明类和函数在多线程程序中的使用。
可重入:若一个程序或子程序可以“安全的被并行执行(Parallel computing)”,则称其为可重入(reentrant或re-entrant)的。
若一个函数是可重入的,则该函数:
1、不能含有静态(全局)非常量数据。
2、不能返回静态(全局)非常量数据的地址。
3、只能处理由调用者提供的数据。
4、不能依赖于单实例模式资源的锁。
5、不能调用(call)不可重入的函数。
线程安全:指某个函数、 函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成。
线程安全的方法可以被多个线程同时调用,即使这些调用都使用到了共享的数据,因为它对共享数据的使用都是序列化,也就是每个序列操作全部完成之前是不可能被别的线程的使用而中断的。可重入的方法也能被多个线程同时调用,但是必须是每个调用都只使用自己的数据。因此,线程安全的方法必定是可重入的,但是可重入的方法未必是线程安全的。
一个类是可重入的,指的是,只要每个线程用的是不同的类的实例。一个类是线程安全的,指的是它的成员方法可以安全的被多个线程同时调用,即使每个线程使用的是相同的类实例。
C++类一般情况下都是可重入的,因为类的成员方法一般都只能操作成员数据,每个线程都是通过自己的类实例调用成员方法,不可能存在别的线程调用同一个类实例的方法。
以下这个类就不是可重入的,很明显,存在静态数据成员。
class Counter
{
public:
Counter() { }
static void increment() { ++n; }
static void decrement() { --n; }
static int value() { return n; }
private:
static int n;
};
class Counter
{
public:
Counter() { }
void increment() { ++n; }
void decrement() { --n; }
int value() { return n; }
private:
int n;
};
简单的改一下,把static去掉就可以成为一个可重入的类。但是它并不是线程安全的,当存在多个线程试图修改n的时候,结果就有可能是未定义的。因为++ 和—操作并不是原子性的操作,其包括以下步骤:
1、 把变量加载到一个寄存器。
2、 寄存器进行自增或自减。
3、 把寄存器的值重新保存到变量中。
假设存在两个线程A和B同时保存了n的旧的的值到寄存器中,再各自进行自增,随后再写回变量,这样便会出现覆盖,n实际也只增加了1。上面说过,就是线程安全方法对数据的操作必须是一序列的不可中断的,就如上面的这个例子,线程A在执行步骤1,2,3必须是连续操作的,在完成3个步骤之前,线程B不允许操作n。因此我们一般情况下必须用互斥变量对数据操作进行加锁。如下
class Counter
{
public:
Counter() { n = 0; }
void increment() { QMutexLocker locker(&mutex); ++n; }
void decrement() { QMutexLocker locker(&mutex); --n; }
int value() const { QMutexLocker locker(&mutex); return n; }
private:
mutable QMutex int n;
};
QMutexLocker 对 n 加锁和解锁,保证了对n的操作是序列化的。我们知道const成员方法不能改变实例的任何数据,所以mutex必须的声明为mutable,因为在value() const进行加锁和解锁改变了mutex。
Qt的许多类都是可重入的,但比不上线程安全的,因为保证线程安全要在每个方法的重复的进行加锁和解锁,那是相当麻烦的。
可重入与线程安全两个概念都关系到函数处理资源的方式。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。
要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
http://blog.csdn.net/hai200501019/article/details/8496989