多线程(6)线程同步

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

什么是线程同步

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

线程同步带来哪些问题

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

线程同步的常用解决方案

1,锁

包括lock关键字和Monitor类型。   使用lock关键字实现:  多线程(6)线程同步

 

多线程(6)线程同步多线程(6)线程同步
 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实现:  多线程(6)线程同步多线程(6)线程同步多线程(6)线程同步
 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

完整代码:

多线程(6)线程同步多线程(6)线程同步
  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代码,如下图: 多线程(6)线程同步

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

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

1,lock语法更简洁。

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

2,信号同步

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

 

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

多线程(6)线程同步

多线程(6)线程同步多线程(6)线程同步
 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,线程阻塞,等待信号。

多线程(6)线程同步

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

多线程(6)线程同步

3,线程安全的集合类

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