线程安全、异常安全、可重入

线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

  • 我们不必担心系统调用的线程安全性,因为系统调用对于用户态来说是原子的,但是要注意系统调用对于内核态的改变可能影响其他线程。
  • 可以说现在glibc库函数大部分都是线程安全的,但是两个或多个函数放到一起就不再安全了,如对某个文件“先seek再read”,这两步操作中间有可能会被打断。
  • 编写线程安全程序的一个难点在于线程安全是不可组合的,就跟C++异常安全也是不可组合的一样。
  • C++的标准库容器和std::string都不是线程安全的,只有std::alocator保证是线程安全的。
  • 只要输入区间是线程安全的,那么泛型函数就是线程安全的。C++标准库中绝大多数泛型算法是线程安全的。
  • C++的iostream不是线程安全的,对于线程安全的stdout输出这个需求,可以改用printf,以达到安全性和输出的原子性。
  • As an example, the POSIX standard requires that C stdio FILE* operations are atomic. POSIX-conforming C libraries (e.g, on Solaris and GNU/Linux) have an internal mutex to serialize operations on FILE*s. However, you still need to not do stupid things like calling fclose(fs) in one thread followed by an access of fs in another.

异常安全性
当异常被抛出时,带有异常安全的函数不会泄漏任何资源,并且不会允许数据败坏。

  • 异常安全函数提供以下三个保证之一,如果它不这样做,它就不具备异常安全性。基本承诺,即如果异常抛出,程序内的任何事物仍然保持在有效状态下;强烈保证,即如果异常被抛出,程序状态不改变;不抛掷保证,即承诺绝不抛出异常,因为它们总是能够完全原先承诺的功能。
  • 解决资源泄漏问题很容易,可以使用RAII技法。
  • 解决数据败坏问题,可以使用智能指针、更改语句次序、CAS策略等。
  • CAS策略原则很简单:为你打算修改的对象做出一份副本,然后在那副本身上做一切必要修改,若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)。实现上通常采用智能指针加上impl技法。
  • “强烈保证”往往能够以CAS实现出来,但“强烈保证”并非对所有函数都可实现(该函数调用其它函数,而被调用的其它函数对非局部性数据有连带影响时)或具备现实意义(耗用时间和空间)。
  • 函数提供的“异常安全性保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
  • 如果系统同有一个(惟有一个)函数不具备异常安全性,整个系统就不具备异常安全性,因为调用那个(不具备异常安全性的)函数有可能导致资源泄漏或数据败坏。

可重入
若一个函数是可重入的,则一般该函数:不能含有静态(或全局)非常量数据,不能返回静态(或全局)非常量数据的地址,只能处理由调用者提供的数据,不能依赖于单实例模式资源的锁,不能调用(call)不可重入的函数(有呼叫(call)到的函数需满足前述条件)。

  • IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的(类似编程中的静态(Static)、全域(Global))资源。
  • 可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
  • 大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供;要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分,一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
  • 可重入函数未必是线程安全的;线程安全函数未必是可重入的。
  • As stated in the description of the character stream locking functions, all standard I/O functions that reference character streams shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of the character streams. Thus, when an application thread locks a character stream, the standard I/O functions cannot be used by other threads to operate on the character stream until the thread holding the lock releases it.

参考:《Java并发编程实战》、《Linux多线程服务端编程》、《Effective C++》、可重入ConcurrencyThread-safety and POSIX.1

原文地址:https://www.cnblogs.com/shuaihanhungry/p/5801856.html