[转]如何写出线程安全的类和函数

什么是线程安全的类和函数,可以被多个线程调用而不会出现数据的错乱的类和函数被叫做线程安全的类和函数,首先导致线程不安全的根本原因是我们函数中或着类中的共享成员变量(如类静态成员变量,全局变量),当我们的函数中或者类中有这些变量时他们都是非线程安全的,当有多个线程调用这些函数或者对象时,就会由于没有对这些变量进行同步操作而产生数据错乱。那么什么样的函数和类可以被多个线程调用而不会出现数据错乱呢。

 一  无状态的

这类函数和类不包含任何其他其他作用域中的变量活着对象,计算过程中的临时状态仅存在于自己的线程栈上的局部变量中,并且只能有当前的线程访问,不会受到其他线程的影响,这些无状态的类和函数时线程安全的。

非线程安全例子:

1: 函数中引用了全局可变变量或者对象。并且函数的运行状态依赖该变量当前状态的影响,此时每次的运行结果是不确定的,受到其他线程的影响,是非线程安全的。

2: 函数中引用或者返回了全局静态变量,局部静态变量,类静态变量,因为静态变量只会初始化一次,他的状态会受到其他线程的影响。

3: 如果一个类中含有可变的静态变量,那么此类也是非线程安全的,当我们在不同的线程中定义了该类的对象时(其实不用定义该类的对象也可),如果在线程中改变了该类的静态成员变量,那么也会造成数据的不一致。

4:调用了线程不安全的函数,那么该函数挥着类也是非线程安全的。

 二  可重入函数

可重入函数不会应用任何共享数据,并且函数参数也是值传递而非指针或者引用传递,他不需要任何的额外的同步操作就能保证它的线程安全性。这类函数成为显式可重入函数,如果我们的指针或者引用参数没有引用共享变量或者全局变量也是可重入的,叫做隐式可重入函数。

一般下面的函数都是不可重入的,也不非线程安全的。

1: 函数内使用了静态的数据结构(这个和上面所说的基本相同)

2: 函数体内使用了malloc和freee函数,(使用了全局内存分配表)

3: 使用标准io函数(使用一些全局的系通资源,printf使用了全局的stdout)

三 线程同步

上面说的都是没有任何同步机制情况下的线程安全函数,如果我们通过一些同步手段,以上的函数或者类会变成线程安全的函数。如果没有同步措施,通常在单线程中如果我们向某个变量写入值然后在读值我们总能得到正确的结果或者说唯一的确定的一个结果,但是当这些操作在不同的线程中执行时情况就会变的非常复杂,我们无法保证正在执行读操作的线程能正确的读到其他线程所做的改变,并且在没有同步机制的环境下,编译器或者处理器会对我们程序的执行顺序进行一些调整,这些调整在单线程环境下不会产生影响,但是在多线程环境中会造成一些意想不到的结果,如果我们的线程之间相互影响,或者我们想让某些线程按照我们的安排来执行,此时如果让他们完全由处理器去控制,就会失去控制,此时就算没有线程之间没有共享数据也会产生错误,因此我们需要一些额外的手段来保证线程的正确执行。

通常我们通过加锁来实现数据的同步和保护,加锁的目的就是保证同一时刻只有一个线程在操作我们所要保户的代码区的共享数据,达到数据的一致性和同步性,但是我们不能盲目的加锁,碰到数据就加锁,锁是需要消耗资源的,如果我们盲目的加锁会造成不必要的资源浪费。因此我们要仔细分析程序中的数据共享性,仅对那些共享的数据代码块加锁即可,但是有时候加锁也并不达到效果,当我们的锁在一个类中时,我们要对类的某个实例进行保护时,通常我们在成员函数中加锁,如果我们不小心把成员变量通过成员函数的返回值或者通过成员函数一个引用或者指针参数,把成员变量传递到了锁的保护范围之外,此时成员变量的正确性已经没有保证了,因此对于锁的使用要很小心。

如果我们想实现线程之间的同步,而不是让处理器来控制线程或者进程的执行,我们可以用信号量pv操作来实现,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目 .信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

下面还有两种也会用到的方式

互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源 安全共享,还能实现不同应用程序的公共资源安全共享 .互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
       事 件: 通过通知操作的方式来保持线程的同步,也可以方便实现对多个线程的优先级比较的操作。

 引文链接:如何写出线程安全的类和函数

时间: 2024-10-30 09:24:50

[转]如何写出线程安全的类和函数的相关文章

《Effective C 》资源管理:条款25--考虑写出一个不抛出异常的swap函数

条款25考虑写出一个不抛出异常的swap函数 条款25:考虑写出一个不抛出异常的swap函数 swap是STL中的标准函数,用于交换两个对象的数值.后来swap成为异常安全编程(exception-safe programming,条款29)的脊柱,也是实现自我赋值(条款11)的一个常见机制.swap的实现如下: namespace std{ template<typename T> void swap(T& a, T& b) { T temp(a); a=b; b=temp;

写出规范化的高可读性的函数代码注释

对代码接触的越多,我越来越想让写出的代码更加perfect,更加高可读性,其中各个函数的代码注释就是非常关键的一步: /** * 函数功能 * @name 名字 * @abstract 申明变量/类/方法 * @access 指明这个变量.类.函数/方法的存取权限 * @author 函数作者的名字和邮箱地址 * @category 组织packages * @copyright 指明版权信息 * @const 指明常量 * @deprecate 指明不推荐或者是废弃的信息MyEclipse编码

写出java.lang.Object类的六个常用方法

java是面向对象的语言,而Object类是java中所有类的顶级父类(根类). 每个类都使用Object类作为超类,所有对象(包括数组)都实现这个类的方法,即使一个类没有用extends明确指出继承于某个类,那么它都默认继承Object类. Object类中提供了很多方法,这里只取其中比较常用的方法做下简述. 1)public String toString()  >>> 获取对象信息的方法 这个方法在打印对象时被调用,将对象的信息变为字符串返回,默认输出对象地址. 举个例子: /**

Effective C++ 条款25 考虑写出一个不抛出异常的swap函数

1. swap是STL的一部分,后来成为异常安全性编程(exception-safe programming)(见条款29)的一个重要脊柱,标准库的swap函数模板定义类似以下: namespace std{ template<typename T> swap(T& lhs,T& rhs){ T temp(lhs); lhs=rhs; rhs=temp; } } 只要T类型支持拷贝构造以及拷贝赋值,标准库swap函数就会调用T的拷贝构造函数和拷贝构造操作符完成值的转换,但对于某

读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数

在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& a) { A temp = a; swap(temp); return *this; } 上面的代码看起来有点麻烦,但它是一个好办法. 我们可以在std里面特化我们的swap class A { private: int a; public: void swap(A& x)//防止写成friend,我

Effective C++笔记_条款25考虑写出一个不抛出异常的swap函数

1 // lib中的swap 2 namespace std { 3 template<typename T> 4 void swap (T& a, T& b) 5 { 6 T temp(a); 7 a = b; 8 b = temp; 9 } 10 } 11 12 // 缺点:需要赋值大量的数据,但是有的时候并不要复制如此多的内容 13 class WidgetImpl { 14 public: 15 //... 16 private: 17 int a, b, c; 18

Discuz论坛写出的php加密解密处理类(代码+使用方法)

PHP加密解密也是常有的事,最近在弄相关的东西,发现discuz论坛里的PHP加密解密处理类代码,感觉挺不错,在用的时候,要参考Discuz论坛的passport相关函数,后面我会附上使用方法,先把类代码帖上来: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <?php /*

设计四个线程,其中共两个线程每次对j增加1,另外两个线程每次对j减少1。循环100次,写出程序。

package cn.usst.DataTest6; /** * 设计四个线程,其中共两个线程每次对j增加1,另外两个线程每次对j减少1.循环100次,写出程序. * @ * */ public class DataTest6 { private int j; public static void main(String[] args) { DataTest6 dt = new DataTest6(); Inc inc = dt.new Inc(); Dec dec = dt.new Dec()

设计四个线程,当中共两个线程每次对j添加1,另外两个线程每次对j降低1。循环100次,写出程序。

package cn.usst.DataTest6; /** * 设计四个线程,当中共两个线程每次对j添加1,另外两个线程每次对j降低1.循环100次,写出程序. * @ * */ public class DataTest6 { private int j; public static void main(String[] args) { DataTest6 dt = new DataTest6(); Inc inc = dt.new Inc(); Dec dec = dt.new Dec()