线程安全
线程安全是程式設計中的术语,指某个函数、函数库在多執行緒环境中被调用时,能够正确地处理多个執行緒之间的公用變數,使程序功能正确完成。
假設有間銀行只有 1000 元,而兩個人同時提領 1000 元時就可能會拿到總計 2000 元的金額。為了避免這個問題,該間銀行提款時應該使用互斥鎖,即意味著針對同一個資源處理時,前一個人提領交易完成後才處理下一筆交易。但這種手法會使得效能降低。
一般来说,线程安全的函数应该为每个调用它的线程分配专门的空间,来储存需要单独保存的状态(如果需要的话),不依赖于“线程惯性”,把多个线程共享的变量正确对待(如,通知编译器该變數为“易失(volatile)”型,阻止其进行一些不恰当的优化),而且,线程安全的函数一般不应该修改全局对象。
很多C库代码(比如某些strtok的实现,它将“多次调用中需要保持不变的状态”储存在静态变量中,导致不恰当的共享)不是线程安全的,在多執行緒环境中调用这些函数时,要进行特别的预防措施,或者寻找别的替代方案。
例子
在下面这段代码中,函数increment_counter是线程安全的,但不是可重入的。
#include <pthread.h>
int increment_counter ()
{
static int counter = 0;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// only allow one thread to increment at a time
++counter;
// store value before any other threads increment it further
int result = counter;
pthread_mutex_unlock(&mutex);
return result;
}
上面的代码中,函数increment_counter可以在多个线程中被调用,因为有一个互斥锁mutex来同步对共享变量counter的访问。但是如果这个函数用在可重入的中断处理程序中,如果在pthread_mutex_lock(&mutex)和pthread_mutex_unlock(&mutex)之间产生另一个调用函数increment_counter的中断,则会第二次执行此函数,此时由于mutex已被lock,函数会在pthread_mutex_lock(&mutex)处阻塞,并且由于mutex没有机会被unlock,阻塞会永远持续下去。简言之,问题在于 pthread 的 mutex 不可重入。
解决办法是设定 PTHREAD_MUTEX_RECURSIVE 属性。然而对于给出的问题而言,专门使用一个 mutex 来保护一次简单的增量操作显然过于昂贵,因此c++11中的原子变量提供了一个可使此函数既线程安全又可重入(而且还更简洁)的替代方案:
#include <atomic>
int increment_counter ()
{
static std::atomic<int> counter(0);
// increment is guaranteed to be done atomically
int result = ++counter;
return result;
}
外部連結
- Thread-safe Tcl Extensions (页面存档备份,存于互联网档案馆)(wiki page)
- Thread-safe design
- Article "Design for thread safety" by Bill Venners
- Article "Write thread-safe servlets" by Phillip Bridgham