线程安全性

优先考虑安全,其次是性能

当我们讨论多线程是否安全的时候,我们在讨论什么?

多线程确实能够带来性能方面的提升,但是我们需要去保证多线程程序的安全性,所谓安全性,主要体现在对于写操作能够获得正确的结果。简单来说,我们希望一个方法或者实例,能够在多线程的环境下使用,而不会出现意想不到的问题。

竞态条件与临界区

当两个线程对同一个资源进行访问且访问顺序敏感时,存在竞态条件(写操作)。导致竞态条件发生的代码区就叫做临界区,例如:

1
2
3
4
5
6
7
8
class Counter {
private:
int count = 0;
public:
void add(int value){
count += value;
}
}

在上面的类中,add函数就是一个临界区。当我们在临界区时,必须考虑如何能够避免竞态条件的产生。

同步&异步

为了进一步说明如何保证线程安全性的问题,我们先来了解何为同步、何为异步。简单来说,同步就是调用方等待调用返回结果后才能继续向后执行;而异步指调用发出后,调用方去做其他事情,通过一些手段例如中断或者回调函数,获得相关的结果。

线程同步与异步

  • 线程同步即当有一个线程在对内存进行操作,其他线程都不可以对这个内存进行操作,只能等到该线程操作完成
  • 线程异步即所有线程均可对内存进行操作,这是线程不安全的

同步方式

互斥量、读写锁、自旋锁(等待其他线程完成)、线程信号、条件变量(特定条件下唤醒线程)。这部分内容详见线程锁条件变量

可重入性与线程安全性

函数和类的可重入性与线程安全性

  • 一个可重入的函数:能同时被多个线程调用,但是函数只使用其内部的非静态局部变量
  • 一个线程安全的函数:能同时被多个线程调用,同时也可以使用共享内存空间,只要该内存空间具有保护措施

一个线程安全的函数一定是可重入的,反之未必。

类似地,一个类是可重入的是指该类的成员函数可以被多个线程同时调用,但是每个线程使用不同的类实例;而一个类是线程安全的是指该类的成员函数可以被多个线程同时调用,尽管所有的线程都使用一个类实例。

可重入性

C++的类大部分是可重入的,因为每个实例只访问其自身的成员,只要能保证一个线程访问单独的一个类实例,即可保证可重入性

1
2
3
4
5
6
7
8
9
10
11
12
class Counter
{
public:
Counter() { n = 0; }

void increment() { ++n; }
void decrement() { --n; }
int value() const { return n; }

private:
int n;
};

线程安全

下面是一个线程安全的类,使用了Qt下的线程锁

1
2
3
4
5
6
7
8
9
10
11
12
13
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 mutex;
int n;
};

参考文献

0%