https://www.cnblogs.com/zhili/archive/2012/07/21/ThreadsSynchronous.html 线程同步
1.c# 的原子操作 可以防止并发。Interlocked
(以保证这段代码的原子性(atomic)操作,即:要么不执行这段代码,要么将这段代码全部执行完毕。防止并发污染数据)
2.线程的优先级
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "B"; threadA.Priority = ThreadPriority.Highest; threadB.Priority = ThreadPriority.BelowNormal; threadB.Start(); threadA.Start(); Thread.CurrentThread.Name = "C"; ThreadMethod(new object()); Console.ReadKey(); } public static void ThreadMethod(object parameter) { for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } }
Thread.CurrentThread.Name = "C"; 当前线程
3. 线程同步
什么是线程安全:
线程安全是指在当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
解决线程同步的方法:
3.0 锁
static void Main(string[] args) { Program pro1 = new Program(); Program pro2 = new Program(); Thread threadA = new Thread(pro1.ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "王文建"; Thread threadB = new Thread(pro2.ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "生旭鹏"; threadA.Start(); threadB.Start(); Console.ReadKey(); } public void ThreadMethod(object parameter) { lock (this) { for (int i = 0; i < 10; i++) { Console.WriteLine("我是:{0},我循环{1}次", Thread.CurrentThread.Name, i); Thread.Sleep(300); } } }
lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。
lock(typeof(Model))锁定的是model类的所有实例。 这里的Model是指某个类名。
lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。
lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
所以,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。
所以,一般情况下,使用私有的、静态的并且是只读的对象。
总结:
1、lock的是必须是引用类型的对象,string类型除外。
2、lock推荐的做法是使用静态的、只读的、私有的对象。
3、保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。
不能锁定字符串,锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。通常,最好避免锁定 public 类型或锁定不受应用程序lock块内控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。lock(typeof(Class))与锁定字符串一样,锁定的对象的作用域的范围太广了。
3.1 使用Monitor类实现线程同步
Lock关键字是Monitor的一种替换用法,lock在IL代码中会被翻译成Monitor.
lock(obj)
{
//代码段
}
就等同于
Monitor.Enter(obj);
//代码段
Monitor.Exit(obj);
Monitor的常用属性和方法:
Enter(Object) 在指定对象上获取排他锁。
Exit(Object) 释放指定对象上的排他锁。
Pulse 通知等待队列中的线程锁定对象状态的更改。
PulseAll 通知所有的等待线程对象状态的更改。
TryEnter(Object) 试图获取指定对象的排他锁。
TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
常用的方法有两个,Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,在使用过程中为了避免获取锁之后因为异常,致锁无法释放,所以需要在try{} catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。
Enter(Object)的用法很简单,看代码
static void Main(string[] args) { Thread threadA = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadA.Name = "A"; Thread threadB = new Thread(ThreadMethod); //执行的必须是无返回值的方法 threadB.Name = "B"; threadA.Start(); threadB.Start(); Thread.CurrentThread.Name = "C"; ThreadMethod(); Console.ReadKey(); } static object obj = new object(); public static void ThreadMethod() { Monitor.Enter(obj); //Monitor.Enter(obj) 锁定对象 try { for (int i = 0; i < 500; i++) { Console.Write(Thread.CurrentThread.Name); } } catch(Exception ex){ } finally { Monitor.Exit(obj); //释放对象 } }
3.2 mutex 互斥锁
4、线程池
上面介绍了介绍了平时用到的大多数的多线程的例子,但在实际开发中使用的线程往往是大量的和更为复杂的,这时,每次都创建线程、启动线程。从性能上来讲,这样做并不理想(因为每使用一个线程就要创建一个,需要占用系统开销);从操作上来讲,每次都要启动,比较麻烦。为此引入的线程池的概念。
好处:
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
在什么情况下使用线程池?
1.单个任务处理的时间比较短
2.需要处理的任务的数量大
线程池最多管理线程数量=“处理器数 * 250”。也就是说,如果您的机器为2个2核CPU,那么CLR线程池的容量默认上限便是1000
通过线程池创建的线程默认为后台线程,优先级默认为Normal。