什么是线程安全的类和函数,可以被多个线程调用而不会出现数据的错乱的类和函数被叫做线程安全的类和函数,首先导致线程不安全的根本原因是我们函数中或着类中的共享成员变量(如类静态成员变量,全局变量),当我们的函数中或者类中有这些变量时他们都是非线程安全的,当有多个线程调用这些函数或者对象时,就会由于没有对这些变量进行同步操作而产生数据错乱。那么什么样的函数和类可以被多个线程调用而不会出现数据错乱呢。
一 无状态的
这类函数和类不包含任何其他其他作用域中的变量活着对象,计算过程中的临时状态仅存在于自己的线程栈上的局部变量中,并且只能有当前的线程访问,不会受到其他线程的影响,这些无状态的类和函数时线程安全的。
非线程安全例子:
1: 函数中引用了全局可变变量或者对象。并且函数的运行状态依赖该变量当前状态的影响,此时每次的运行结果是不确定的,受到其他线程的影响,是非线程安全的。
2: 函数中引用或者返回了全局静态变量,局部静态变量,类静态变量,因为静态变量只会初始化一次,他的状态会受到其他线程的影响。
3: 如果一个类中含有可变的静态变量,那么此类也是非线程安全的,当我们在不同的线程中定义了该类的对象时(其实不用定义该类的对象也可),如果在线程中改变了该类的静态成员变量,那么也会造成数据的不一致。
4:调用了线程不安全的函数,那么该函数挥着类也是非线程安全的。
二 可重入函数
可重入函数不会应用任何共享数据,并且函数参数也是值传递而非指针或者引用传递,他不需要任何的额外的同步操作就能保证它的线程安全性。这类函数成为显式可重入函数,如果我们的指针或者引用参数没有引用共享变量或者全局变量也是可重入的,叫做隐式可重入函数。
一般下面的函数都是不可重入的,也不非线程安全的。
1: 函数内使用了静态的数据结构(这个和上面所说的基本相同)
2: 函数体内使用了malloc和freee函数,(使用了全局内存分配表)
3: 使用标准io函数(使用一些全局的系通资源,printf使用了全局的stdout)
三 线程同步
上面说的都是没有任何同步机制情况下的线程安全函数,如果我们通过一些同步手段,以上的函数或者类会变成线程安全的函数。如果没有同步措施,通常在单线程中如果我们向某个变量写入值然后在读值我们总能得到正确的结果或者说唯一的确定的一个结果,但是当这些操作在不同的线程中执行时情况就会变的非常复杂,我们无法保证正在执行读操作的线程能正确的读到其他线程所做的改变,并且在没有同步机制的环境下,编译器或者处理器会对我们程序的执行顺序进行一些调整,这些调整在单线程环境下不会产生影响,但是在多线程环境中会造成一些意想不到的结果,如果我们的线程之间相互影响,或者我们想让某些线程按照我们的安排来执行,此时如果让他们完全由处理器去控制,就会失去控制,此时就算没有线程之间没有共享数据也会产生错误,因此我们需要一些额外的手段来保证线程的正确执行。
通常我们通过加锁来实现数据的同步和保护,加锁的目的就是保证同一时刻只有一个线程在操作我们所要保户的代码区的共享数据,达到数据的一致性和同步性,但是我们不能盲目的加锁,碰到数据就加锁,锁是需要消耗资源的,如果我们盲目的加锁会造成不必要的资源浪费。因此我们要仔细分析程序中的数据共享性,仅对那些共享的数据代码块加锁即可,但是有时候加锁也并不达到效果,当我们的锁在一个类中时,我们要对类的某个实例进行保护时,通常我们在成员函数中加锁,如果我们不小心把成员变量通过成员函数的返回值或者通过成员函数一个引用或者指针参数,把成员变量传递到了锁的保护范围之外,此时成员变量的正确性已经没有保证了,因此对于锁的使用要很小心。
如果我们想实现线程之间的同步,而不是让处理器来控制线程或者进程的执行,我们可以用信号量pv操作来实现,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目 .信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
下面还有两种也会用到的方式
互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源 安全共享,还能实现不同应用程序的公共资源安全共享 .互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
事 件: 通过通知操作的方式来保持线程的同步,也可以方便实现对多个线程的优先级比较的操作。
引文链接:如何写出线程安全的类和函数