一般的说,函数的多线程安全性是指,一个函数在被调用过程中,还未返回时,又再次被其他线程调用的情况下,函数执行结果的可靠性。如果结果是可靠的,则称这个函数是多线程安全的;如果结果是不可靠的,则称这个函数是非多线程安全的,原因是多线程冲突。
函数的多线程冲突在内核编程中远比用户态应用程序的编程要常见。因此读者会常常听到忠告:要注意函数的多线程安全性。但是严格地去保证每个函数的多线程安全性是浪费的,甚至有时是不可能的。因此我们需要判断何时需要保证函数的多线程安全性。读者可以通过下面几条规则来简单地判断:
规则1:可能运行于多线程环境的函数,必须是多线程安全的。只运行于单线程环境的函数,则不需要多线程安全性。
这是因为存在多线程同时调用该函数的可能,此时必须保证函数的可靠性。那么如何判断函数的运行环境是否是多线程环境呢?可以根据规则2和规则3进行判断。
规则2:如果函数A的所有的调用源只运行于同一单线程环境,则函数A也是只运行在单线程环境的。
规则3:如果函数A的其中一个调用源是可能运行在多线程环境的,或者多个调用源可能运行于不同的可并发的多个线程环境,而且调用路径上没有采取多线程序列化成单线程的强制措施,则函数A也是可能运行在多线程环境的。
规则4:如果在函数A所有的可能运行于多线程环境的调用路径上,都有多线程序列化成单线程的强制措施,则函数A是运行在单线程环境的。
这里所谓的“多线程序列化成单线程的强制措施”是指如互斥体、自旋锁(读者在后面会见到简单自旋锁的使用)等同步手段。
从上面的规则中可以得到一条推论,就是如果要在多线程环境下调用一个不可重入的函数,则必须在调用路径上采取多线程序列化成单线程的强制措施。
从上面的规则可以看出,是否要保证可重入性最终由调用源和调用路径决定。下面是内核编程中主要调用源的运行环境,如表2-6所示。
表2-6 内核代码主要调用源的运行环境
调用源 | 运行环境 | 原 因 |
DriverEntry,DriverUnload | 单线程 | 这两个函数由系统进程的单一线程调用。不会出现多线程同时调用的情况 |
各种分发函数 | 多线程 | 没有任何文档保证分发函数是不会被多线程同时调用的。此外,分发函数不会和DriverEntry并发,但可能和DriverUnload并发 |
完成函数 | 多线程 | 完成函数随时可能被未知的线程调用 |
各种NDIS回调函数 | 多线程 | 和完成函数相同 |
前面的规则结合这个表,基本上可以判断任意一段代码是否需要多线程安全性。 那么如何保证多线程安全性呢?规则如下:
规则5:只使用函数内部资源,完全不使用全局变量、静态变量或者其他全局性资源的函数是多线程安全的。
规则6:如果对某个全局变量或者静态变量的所有访问都被强制的同步手段限制为同一时刻只有一个线程访问,则即使使用了这些全局变量和静态变量,对函数的多线程安全性也是没有影响的。可以等同于内部变量,然后根据规则5判定。
其他的情况则是非多线程安全的。这也是读者会发现在本书从第3章开始的许多代码中,常常出现使用自旋锁来作为同步手段的原因。
转自:http://book.51cto.com/art/200905/125753.htm