上一篇文章已经介绍了,线程是对CPU的模拟和抽象,因为一台机器只有一个CPU,又要执行多个应用的代码,为了让上层应用不考虑这些细节,而使用线程这么个东西抽象一下,这样让上层应用觉得整个CPU都是它的。但CPU毕竟只有一个(或是有限的),那么就必定存在线程的切换。这也就涉及线程的状态转换:
主意这里的箭头有双向的,有单向的。
线程在运行时可能由于需要某种资源而暂时停止运行,这称之为阻塞,当需要的资源得到满足时线程的状态会变成就绪,如果这个时候正好有CPU空闲或者正好线程调度到这个线程上,那么该线程就会马上执行。线程在运行时还有可能因为自己的时间片用完了,线程调度程序安排别的线程执行(上下文切换),而从运行状态变成就绪状态。就绪状态也可以稍后调度为运行状态。线程调度的算法有很多种,这在操作系统领域有很多文献专门来描述这个。
死锁(deadlock)
在写多线程相关的书籍或者文章中,恐怕死锁这个词出现的频率最高。那死锁到底是一个什么意思呢?
为了解释这个概念,我们假设有两个线程T1和T2,有两个资源R1和R2。在某一时刻,T1拥有R1,T2拥有R2。但是这两个线程贪得无厌,T1还需要R2才能运行所以阻塞了,而T2需要R1才能运行所以也阻塞了。所以这两个线程总是不断地尝试获取永远也得不到的东西互不相让,这种状态除非有外力的介入,不然不会打破。这就是死锁。死锁的危害性非常大,有可能造成整个系统的宕机。
我们来看下面这段程序,来演示一下如何弄出个死锁出来(该程序仅仅为了演示目的,实际中不要如此编码):
1: using System;
2: using System.Threading;
3: namespace DeadLock
4: {
class Program
6: {
void Main(string[] args)
8: {
new Test();
new Thread(t.test1);
new Thread(t.test2);
12:
13: t1.Start();
14: t2.Start();
15:
16: Console.ReadLine();
17:
18: }
19: }
20:
class Test
22: {
new object();
new object();
25:
void test1()
27: {
28:
, Thread.CurrentThread.ManagedThreadId.ToString()));
30: lock (resource1)
31: {
, Thread.CurrentThread.ManagedThreadId.ToString()));
33: Thread.Sleep(500);
, Thread.CurrentThread.ManagedThreadId.ToString()));
35: lock (resource2)
36: {
, Thread.CurrentThread.ManagedThreadId.ToString()));
38: }
39: }
40: }
41:
void test2()
43: {
, Thread.CurrentThread.ManagedThreadId.ToString()));
45: lock (resource2)
46: {
, Thread.CurrentThread.ManagedThreadId.ToString()));
48: Thread.Sleep(500);
, Thread.CurrentThread.ManagedThreadId.ToString()));
50: lock (resource1)
51: {
, Thread.CurrentThread.ManagedThreadId.ToString()));
53: }
54: }
55: }
56: }
57: }