多线程(6)线程同步

2018-06-18 04:12:25来源:未知 阅读 ()

新老客户大回馈,云服务器低至5折

   使用多线程很容易,但是如果多个线程同时访问一个共享资源时而不加以控制,就会导致数据损坏。所以多线程并发时,必须要考虑线程同步(或称线程安全)的问题。 

什么是线程同步

多个线程同时访问共享资源时,使多个线程顺序(串行)访问共享资源的机制。
注意:
1,共享资源,比如全局变量和静态变量。
2,访问,一般指写操作,读操作无需考虑线程同步。
3,串行,指当一个线程正在访问共享资源时,其它线程等待,直到该线程释放锁。

线程同步带来哪些问题

如果能保证多个线程不会同时访问共享资源,那么就不需要考虑线程同步。
虽然线程同步能保证多线程同时访问共享数据时线程安全,但是同时也会带来以下问题:
1,使用起来繁琐,因为必须找出代码中所有可能由多个线程同时访问的共享数据,并且要用额外的代码将这些代码包围起来,获取和释放一个线程同步锁,而一旦有一处忘记用锁包围,共享数据就会被损坏。
2,损害性能,因为获取和释放一个锁是需要时间的。
3,可能会造成更多的线程被创建,由于线程同步锁一次只允许一个线程访问共享资源,当线程池线程试图获取一个暂时无法获取的锁时,线程池就会创建一个新的线程。
所以,要从设计上尽可能地避免线程同步,实在不能避免的再考虑线程同步。

线程同步的常用解决方案

1,锁

包括lock关键字和Monitor类型。
 
使用lock关键字实现:
 

 

 1 /// <summary>
 2 /// 线程同步计算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局变量
 8     /// </summary>
 9     public int Result = 0;
10 
11     private static readonly object lockObj = new object();
12 
13     public override void Increase()
14     {
15         lock (lockObj)
16         {
17             Result++;
18         }
19     }
20 
21     public override void Decrease()
22     {
23         lock (lockObj)
24         {
25             Result--;
26         }
27     }
28 }
View Code
需要注意的是:
1,lock锁定的对象必须是引用类型,不能是值类型。因为值类型传入会发生装箱,这样每次lock的将是一个不同的对象,就没有办法实现多线程同步了。
2,避免使用public类型的对象,这样很容易导致死锁。因为其它代码也有可能锁定该对象。
3,避免锁定字符串,因为字符串会被CLR暂留(也就是说两个变量的字符串内容相同,.net会把暂留的字符串对象分配给变量),导致应用程序中锁定的是同一个对象,造成死锁。
 
使用Monitor实现:
 
 1 /// <summary>
 2 /// 线程同步计算器
 3 /// </summary>
 4 public class SyncCounter : CounterBase
 5 {
 6     /// <summary>
 7     /// 全局变量
 8     /// </summary>
 9     public int Result = 0;
10 
11     private static readonly object lockObj = new object();
12 
13     public override void Increase()
14     {
15         Monitor.Enter(lockObj);
16         try
17         {
18             Result++;
19         }
20         finally
21         {
22             Monitor.Exit(lockObj);
23         }
24     }
25 
26     public override void Decrease()
27     {
28         Monitor.Enter(lockObj);
29         try
30         {
31             Result--;
32         }
33         finally
34         {
35             Monitor.Exit(lockObj);
36         }
37     }
38 }
View Code

完整代码:

  1 namespace ConsoleApplication28
  2 {
  3     class Program
  4     {
  5         static void Main(string[] args)
  6         {
  7             //同时发起3个异步线程
  8             Console.WriteLine("普通(非线程同步)计算器测试...");
  9             var normalCounter = new NormalCounter();
 10             var tasks = new List<Task>();
 11             var task1 = Task.Factory.StartNew(() =>
 12             {
 13                 TestCounter(normalCounter);
 14             });
 15             tasks.Add(task1);
 16 
 17             var task2 = Task.Factory.StartNew(() =>
 18             {
 19                 TestCounter(normalCounter);
 20             });
 21             tasks.Add(task2);
 22 
 23             var task3 = Task.Factory.StartNew(() =>
 24             {
 25                 TestCounter(normalCounter);
 26             });
 27             tasks.Add(task3);
 28 
 29 
 30             Task.WaitAll(tasks.ToArray());
 31             Console.WriteLine("NormalCounter.Result:" + normalCounter.Result);
 32             Console.WriteLine("*******************************************");
 33 
 34             Console.WriteLine("线程同步计算器测试...");
 35             var syncCounter = new SyncCounter();
 36             var tasks1 = new List<Task>();
 37             task1 = Task.Factory.StartNew(() =>
 38             {
 39                 TestCounter(syncCounter);
 40             });
 41             tasks1.Add(task1);
 42 
 43             task2 = Task.Factory.StartNew(() =>
 44             {
 45                 TestCounter(syncCounter);
 46             });
 47             tasks1.Add(task2);
 48 
 49             task3 = Task.Factory.StartNew(() =>
 50             {
 51                 TestCounter(syncCounter);
 52             });
 53             tasks1.Add(task3);
 54 
 55             Task.WaitAll(tasks1.ToArray());
 56             Console.WriteLine("SyncCounter.Result:" + syncCounter.Result);
 57 
 58             Console.ReadKey();
 59         }
 60 
 61         /// <summary>
 62         /// 
 63         /// </summary>
 64         /// <param name="counter"></param>
 65         static void TestCounter(CounterBase counter)
 66         {
 67             //100000次加减
 68             for (int i = 0; i < 100000; i++)
 69             {
 70                 counter.Increase();
 71                 counter.Decrease();
 72             }
 73         }
 74     }
 75 
 76     /// <summary>
 77     /// 计算器基类
 78     /// </summary>
 79     public abstract class CounterBase
 80     {
 81         /// <summary>
 82         /// 83         /// </summary>
 84         public abstract void Increase();
 85 
 86         /// <summary>
 87         /// 88         /// </summary>
 89         public abstract void Decrease();
 90     }
 91 
 92     /// <summary>
 93     /// 普通计算器
 94     /// </summary>
 95     public class NormalCounter : CounterBase
 96     {
 97         /// <summary>
 98         /// 全局变量
 99         /// </summary>
100         public int Result = 0;
101 
102         public override void Increase()
103         {
104             Result++;
105         }
106 
107         public override void Decrease()
108         {
109             Result--;
110         }
111 
112     }
113 
114     /// <summary>
115     /// 线程同步计算器
116     /// </summary>
117     public class SyncCounter : CounterBase
118     {
119         /// <summary>
120         /// 全局变量
121         /// </summary>
122         public int Result = 0;
123 
124         private static readonly object lockObj = new object();
125 
126         public override void Increase()
127         {
128             lock (lockObj)
129             {
130                 Result++;
131             }
132         }
133 
134         public override void Decrease()
135         {
136             lock (lockObj)
137             {
138                 Result--;
139             }
140         }
141     }
142 }
View Code

  

lock关键字揭密:

 通过查看lock关键字生成的IL代码,如下图:

从上图可以得出以下结论:

lock关键字内部就是使用Monitor类(或者说lock关键字是Monitor的语法糖),使用lock关键字比直接使用Monitor更好,原因有二。

1,lock语法更简洁。

2,lock确保了即使代码抛出异常,也可以释放锁,因为在finally中调用了Monitor.Exit方法。 

2,信号同步

信号同步机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。关系如下图。

 

下面是使用信号同步机制的一个简单的例子,如下代码:

 1 namespace WindowsFormsApplication1
 2 {
 3     public partial class Form1 : Form
 4     {
 5         //信号
 6         AutoResetEvent autoResetEvent = new AutoResetEvent(false);
 7 
 8         public Form1()
 9         {
10             InitializeComponent();
11 
12             CheckForIllegalCrossThreadCalls = false;
13         }
14 
15         /// <summary>
16         /// 开始
17         /// </summary>
18         /// <param name="sender"></param>
19         /// <param name="e"></param>
20         private void button1_Click(object sender, EventArgs e)
21         {
22             Task.Factory.StartNew(() => 
23             {
24                 this.richTextBox1.Text+="线程启动..." + Environment.NewLine;
25                 this.richTextBox1.Text += "开始处理一些实际的工作" + Environment.NewLine;
26                 Thread.Sleep(3000);
27 
28                 this.richTextBox1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
29                 autoResetEvent.WaitOne();
30                 this.richTextBox1.Text += "我继续做一些工作,然后结束了!";
31             });
32         }
33 
34         /// <summary>
35         /// 信号同步
36         /// </summary>
37         /// <param name="sender"></param>
38         /// <param name="e"></param>
39         private void button2_Click(object sender, EventArgs e)
40         {
41             //给在autoResetEvent上等待的线程一个信号
42             autoResetEvent.Set();
43         }
44     }
45 }
View Code

运行效果:

1,线程阻塞,等待信号。

2,主线程发送信号,让线程继续执行。

3,线程安全的集合类

我们也可以通过使用.net提供的线程安全的集合类来保证线程安全。在命名空间:System.Collections.Concurrent下。
主要包括:
  • ConcurrentQueue 线程安全版本的Queue【常用】
  • ConcurrentStack线程安全版本的Stack
  • ConcurrentBag线程安全的对象集合
  • ConcurrentDictionary线程安全的Dictionary【常用】
 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:此请求已被阻止,因为当用在 GET 请求中时,会将敏感信息透漏给

下一篇:[翻译] 编写高性能 .NET 代码--第二章 GC -- 将长生命周期对象和