多线程小结(三)线程同步总结

对线程同步做一个总结,

一般来说,线程同步比较让人纠结的地方在于它是许多线程共用一段代码的,而且什么时候谁用谁不用,也基本是不可控制不可预料的,那么对于它们可能会同时访问并更改的数据,就需要加锁了。加锁就是将一段代码变为临界区  ——  一段在同一时候只被一个线程进入/执行的代码,加锁的方式一般有两种,Lock关键字

C#提供lock关键字实现临界区,MSDN里给出的用法:

Object thisLock= new Object();

lock (thisLock)
{

   // Critical code section

此方法的难点是如何选择锁的对象,比如写日志类,如果是静态对象锁自身,那么即使是不冲突的操作,也会互斥,白白浪费了cpu的时间片(像是你所在的a车道可以走了,但非要等到b车道空闲了,你才开始走a车道)

普通的需要实例化的类也要注意,如果是锁this,那么一旦这个对象有多个实例,那么多个实例的操作就变成不互斥了,因为它们锁住的不是同一个thisLock同时也可以在外部声明一个object,将引用传入临界区,让临界区锁住这个对象,但这种方式比较灵活,但使用起来也需要很小心的维护这个引用。

还有种方法是使用 Monitor

System.Objectobj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
  DoSomething();
}
finally
{
  System.Threading.Monitor.Exit(obj);
}

Enter和exit 望文生义,就是进入临界区和离开临界区,其实和lock使用起来没什么区别,只是多了个finally,在这里你可以多做一些事情,类似于using的自动dispose和寻常的try  finally dispose的区别。不过monitor还有一些其他方法,如Wait和Pulse,可以起到暂停线程和通知拥有锁对象的线程的作用,但我不明白的是,为什么这两个也是静态方法。对于它的运行机制,还不太了解。

还有mutex也可以完成同步,但是用法比较复杂,它的特性是可以在进程中同步,所以线程同步一般不会用它。

 System .Object 
   System .MarshalByRefObject 
     System.Threading .WaitHandle 
       System.Threading .Mutex 

也可以合理利用volatile 关键字,

msdnvolatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

volatile 修饰符通常用于由多个线程访问但不使用 lock 语句对访问进行序 列化的字段。

volatile 关键字可应用于以下类型的字段:

  • 引用类型。

  • 指针类型(在不安全的上下文中)。请注意,虽然指针本身可以是可变的,但是它指向的对象不能是可变的。换句话说,您无法声明“指向可变对象的指针”。

  • 整型,如 sbyte、byte、short、ushort、int、uint、char、float 和 bool。

  • 具有整数基类型的枚举类型。

  • 已知为引用类型的泛型类型参数。

可变关键字仅可应用于类或结构字段。不能将局部变量声明为 volatile。


线程同步易出错的地方是,全局变量和静态变量,这个必须非常小心。

前面也提到了,线程是共享资源的,所以每一个线程都是访问的同一个全局变量和静态变量(局部变量无此隐患)。

一个很简单的示例,

 int 全局变量 i

1 {    if(i%2==1)

2 {sysout(i +"是单数")}

3 else { i 是双数}

4 i++   ;

}  这段代码在多个线程里面被并发执行,查看输出,会发现很多错误,因为1执行完毕,进入2或3,但还未执行,这时其他线程可能执行了4,所以i被++了。



原文地址:https://www.cnblogs.com/suijing/p/3379392.html