多线程(基础篇3)

  在上一篇多线程(基础篇2)中,我们主要讲述了确定线程的状态、线程优先级、前台线程和后台线程以及向线程传递参数的知识,在这一篇中我们将讲述如何使用C#的lock关键字锁定线程、使用Monitor锁定线程以及线程中的异常处理。

九、使用C#的lock关键字锁定线程

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,然后修改为如下代码:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4
 5 namespace Recipe09
 6 {
 7     abstract class CounterBase
 8     {
 9         public abstract void Increment();
10         public abstract void Decrement();
11     }
12
13     class Counter : CounterBase
14     {
15         public int Count { get; private set; }
16
17         public override void Increment()
18         {
19             Count++;
20         }
21
22         public override void Decrement()
23         {
24             Count--;
25         }
26     }
27
28     class CounterWithLock : CounterBase
29     {
30         private readonly object syncRoot = new Object();
31
32         public int Count { get; private set; }
33
34         public override void Increment()
35         {
36             lock (syncRoot)
37             {
38                 Count++;
39             }
40         }
41
42         public override void Decrement()
43         {
44             lock (syncRoot)
45             {
46                 Count--;
47             }
48         }
49     }
50
51     class Program
52     {
53         static void TestCounter(CounterBase c)
54         {
55             for (int i = 0; i < 100000; i++)
56             {
57                 c.Increment();
58                 c.Decrement();
59             }
60         }
61
62         static void Main(string[] args)
63         {
64             WriteLine("Incorrect counter");
65             var c1 = new Counter();
66             var t1 = new Thread(() => TestCounter(c1));
67             var t2 = new Thread(() => TestCounter(c1));
68             var t3 = new Thread(() => TestCounter(c1));
69             t1.Start();
70             t2.Start();
71             t3.Start();
72             t1.Join();
73             t2.Join();
74             t3.Join();
75             WriteLine($"Total count: {c1.Count}");
76
77             WriteLine("--------------------------");
78
79             WriteLine("Correct counter");
80             var c2 = new CounterWithLock();
81             t1 = new Thread(() => TestCounter(c2));
82             t2 = new Thread(() => TestCounter(c2));
83             t3 = new Thread(() => TestCounter(c2));
84             t1.Start();
85             t2.Start();
86             t3.Start();
87             t1.Join();
88             t2.Join();
89             t3.Join();
90             WriteLine($"Total count: {c2.Count}");
91         }
92     }
93 }

3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

  在第65行代码处,我们创建了Counter类的一个对象,该类定义了一个简单的counter变量,该变量可以自增1和自减1。然后在第66~68行代码处,我们创建了三个线程,并利用lambda表达式将Counter对象传递给了“TestCounter”方法,这三个线程共享同一个counter变量,并且对这个变量进行自增和自减操作,这将导致结果的不正确。如果我们多次运行这个控制台程序,它将打印出不同的counter值,有可能是0,但大多数情况下不是。

  发生这种情况是因为Counter类是非线程安全的。我们假设第一个线程在第57行代码处执行完毕后,还没有执行第58行代码时,第二个线程也执行了第57行代码,这个时候counter的变量值自增了2次,然后,这两个线程同时执行了第58行处的代码,这会造成counter的变量只自减了1次,因此,造成了不正确的结果。

  为了确保不发生上述不正确的情况,我们必须保证在某一个线程访问counter变量时,另外所有的线程必须等待其执行完毕才能继续访问,我们可以使用lock关键字来完成这个功能。如果我们在某个线程中锁定一个对象,其他所有线程必须等到该线程解锁之后才能访问到这个对象,因此,可以避免上述情况的发生。但是要注意的是,使用这种方式会严重影响程序的性能。更好的方式我们将会在仙童同步中讲述。

十、使用Monitor锁定线程

  在这一小节中,我们将描述一个多线程编程中的常见的一个问题:死锁。我们首先创建一个死锁的示例,然后使用Monitor避免死锁的发生。

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写代码如下:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 lock (lock1)
33                 {
34                     WriteLine("Acquired a protected resource succesfully");
35                 }
36             }
37         }
38     }
39 }

3、运行该控制台应用程序,运行效果如下图所示:

  在上述结果中我们可以看到程序发生了死锁,程序一直结束不了。

  在第10~19行代码处,我们定义了一个名为“LockTooMuch”的方法,在该方法中我们锁定了第一个对象lock1,等待1秒钟后,希望锁定第二个对象lock2。

  在第26行代码处,我们创建了一个新的线程来执行“LockTooMuch”方法,然后立即执行第28行代码。

  在第28~32行代码处,我们在主线程中锁定了对象lock2,然后等待1秒钟后,希望锁定第一个对象lock1。

  在创建的新线程中我们锁定了对象lock1,等待1秒钟,希望锁定对象lock2,而这个时候对象lock2已经被主线程锁定,所以新建线程会等待对象lock2被主线程解锁。然而,在主线程中,我们锁定了对象lock2,等待1秒钟,希望锁定对象lock1,而这个时候对象lock1已经被创建的线程锁定,所以主线程会等待对象lock1被创建的线程解锁。当发生这种情况的时候,死锁就发生了,所以我们的控制台应用程序目前无法正常结束。

4、要避免死锁的发生,我们可以使用“Monitor.TryEnter”方法来替换lock关键字,“Monitor.TryEnter”方法在请求不到资源时不会阻塞等待,可以设置超时时间,获取不到直接返回false。修改代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5
 6 namespace Recipe10
 7 {
 8     class Program
 9     {
10         static void LockTooMuch(object lock1, object lock2)
11         {
12             lock (lock1)
13             {
14                 Sleep(1000);
15                 lock (lock2)
16                 {
17                 }
18             }
19         }
20
21         static void Main(string[] args)
22         {
23             object lock1 = new object();
24             object lock2 = new object();
25
26             new Thread(() => LockTooMuch(lock1, lock2)).Start();
27
28             lock (lock2)
29             {
30                 WriteLine("This will be a deadlock!");
31                 Sleep(1000);
32                 //lock (lock1)
33                 //{
34                 //    WriteLine("Acquired a protected resource succesfully");
35                 //}
36                 if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
37                 {
38                     WriteLine("Acquired a protected resource succesfully");
39                 }
40                 else
41                 {
42                     WriteLine("Timeout acquiring a resource!");
43                 }
44             }
45         }
46     }
47 }

5、运行该控制台应用程序,运行效果如下图所示:

  此时,我们的控制台应用程序就避免了死锁的发生。

十一、处理异常

  在这一小节中,我们讲述如何在线程中正确地处理异常。正确地将try/catch块放置在线程内部是非常重要的,因为在线程外部捕获线程内部的异常通常是不可能的。

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,修改代码如下所示:

 1 using System;
 2 using System.Threading;
 3 using static System.Console;
 4 using static System.Threading.Thread;
 5
 6 namespace Recipe11
 7 {
 8     class Program
 9     {
10         static void BadFaultyThread()
11         {
12             WriteLine("Starting a faulty thread...");
13             Sleep(TimeSpan.FromSeconds(2));
14             throw new Exception("Boom!");
15         }
16
17         static void FaultyThread()
18         {
19             try
20             {
21                 WriteLine("Starting a faulty thread...");
22                 Sleep(TimeSpan.FromSeconds(1));
23                 throw new Exception("Boom!");
24             }
25             catch(Exception ex)
26             {
27                 WriteLine($"Exception handled: {ex.Message}");
28             }
29         }
30
31         static void Main(string[] args)
32         {
33             var t = new Thread(FaultyThread);
34             t.Start();
35             t.Join();
36
37             try
38             {
39                 t = new Thread(BadFaultyThread);
40                 t.Start();
41             }
42             catch (Exception ex)
43             {
44                 WriteLine(ex.Message);
45                 WriteLine("We won‘t get here!");
46             }
47         }
48     }
49 }

3、运行该控制台应用程序,运行效果如下图所示:

  在第10~15行代码处,我们定义了一个名为“BadFaultyThread”的方法,在该方法中抛出一个异常,并且没有使用try/catch块捕获该异常。

  在第17~29行代码处,我们定义了一个名为“FaultyThread”的方法,在该方法中也抛出一个异常,但是我们使用了try/catch块捕获了该异常。

  在第33~35行代码处,我们创建了一个线程,在该线程中执行了“FaultyThread”方法,我们可以看到在这个新创建的线程中,我们正确地捕获了在“FaultyThread”方法中抛出的异常。

  在第37~46行代码处,我们又新创建了一个线程,在该线程中执行了“BadFaultyThread”方法,并且在主线程中使用try/catch块来捕获在新创建的线程中抛出的异常,不幸的的是我们在主线程中无法捕获在新线程中抛出的异常。

  由此可以看到,在一个线程中捕获另一个线程中的异常通常是不可行的。

  至此,多线程(基础篇)我们就讲述到这儿,之后我们将讲述线程同步相关的知识,敬请期待!

  源码下载

时间: 2024-10-10 17:39:38

多线程(基础篇3)的相关文章

Linux C 程序设计多线程基础篇

   Linux C 程序设计多线程基础篇 题记:因为 Linux 网络入侵检测系统的设计与实现希望使用多线程,因此希望系统的学习一下 Linux C程序设计多线程的知识 注意事项:因为 pthraed 库不是 Linux 系统默认的库,因此在进行多线程开发的时候,需要加上头文件#include <pthread.h>,编译时要加参数 -lpthread;了:gcc thread.c -o thread -lpthread. 进程和线程: 进程是程序执行,资源分配的基本单位,每个进程都拥有自己

多线程-基础篇

线程概念 线程就是程序中单独顺序的流控制. 线程本身不能运行,它只能用于程序中. 说明:线程是程序内的顺序控制流,只能使用分配给程序的资源和环境. 进程 进程:执行中的程序. 程序是静态的概念,进程是动态的概念. 一个进程可以包含一个或多个线程. 一个进程至少要包含一个线程. 线程与进程的区别 多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响. 线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换负担比进程切换的负担要小.

并发编程之多线程基础篇及面试

线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行.也可以把它理解为代码运行的上下文.所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务.通常由操作系统负责多个线程的调度和执行. 使用线程可以把占据时间长的程序中的任务放到后台去处理,程序的运行速度可能加快,在一些等待的任务实现上如用户输入.文件读写和网络收发数据等,线程就比较有用了.在这种情况下可以释放一些珍贵的资源如内存占用等等. 如果有大量

多线程基础篇(一)

线程之间方法区和堆内存共享,栈内存不共享;哪个线程调用sleep()方法,哪个线程就进入睡眠状态,与哪个对象调用该方法无关.start()方法的作用是创建一个线程的栈内存,该方法与普通方法相同,执行完立刻销毁. package test1; public class RacerRunnable implements Runnable{ /** * 龟兔赛跑多线程 */ public String winner; public void run() { for(int step = 1; step

多线程(基础篇1)

在多线程这一系列文章中,我们将讲述C#语言中多线程的相关知识,在多线程(基础篇)中我们将学习以下知识点: 创建线程 中止线程 线程等待 终止线程 确定线程的状态 线程优先级 前台线程和后台线程 向线程传递参数 使用C#的lock关键字锁定线程 使用Monitor锁定线程 处理异常 一.创建线程 在整个系列文章中,我们主要使用Visual Studio 2015作为线程编程的主要工具.在C#语言中创建.使用线程只需要按以下步骤编写即可: 1.启动Visual Studio 2016,新建一个控制台

多线程的那点儿事(基础篇)

多线程编程是现代软件技术中很重要的一个环节.要弄懂多线程,这就要牵涉到多进程?当然,要了解到多进程,就要涉及到操作系统.不过大家也不要紧张,听我慢慢道来.这其中的环节其实并不复杂. (1)单CPU下的多线程 在没有出现多核CPU之前,我们的计算资源是唯一的.如果系统中有多个任务要处理的话,那么就需要按照某种规则依次调度这些任务进行处理.什么规则呢?可以是一些简单的调度方法,比如说 1)按照优先级调度 2)按照FIFO调度 3)按照时间片调度等等 当然,除了CPU资源之外,系统中还有一些其他的资源

多线程(基础篇1)转载

在多线程这一系列文章中,我们将讲述C#语言中多线程的相关知识,在多线程(基础篇)中我们将学习以下知识点: 创建线程 中止线程 线程等待 终止线程 确定线程的状态 线程优先级 前台线程和后台线程 向线程传递参数 使用C#的lock关键字锁定线程 使用Monitor锁定线程 处理异常 一.创建线程 在整个系列文章中,我们主要使用Visual Studio 2015作为线程编程的主要工具.在C#语言中创建.使用线程只需要按以下步骤编写即可: 1.启动Visual Studio 2016,新建一个控制台

Spark性能优化指南——基础篇

前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作,应用范围与前景非常广泛.在美团•大众点评,已经有很多同学在各种项目中尝试使用Spark.大多数同学(包括笔者在内),最初开始尝试使用Spark的原因很简单,主要就是为了让大数据计算作业的执行速度更快.性能更高. 然而,通过Spark开发出高性能的大数据计算作业,并不是那么简单的.如果没有对Spar

秒杀多线程第一篇 多线程笔试面试题汇总 ZZ 【多线程】

http://blog.csdn.net/morewindows/article/details/7392749 系列前言 本系列是本人参加微软亚洲研究院,腾讯研究院,迅雷面试时整理的,另外也加入一些其它IT公司如百度,阿里巴巴的笔试面试题目,因此具有很强的针对性.系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”.有了“招式”和“内功心法”,相信你也能对多线程挥洒自如,在笔试面试中顺利的秒杀多线程试题. ----------------------