C#多线程之旅(1)——介绍和基本概念
阅读目录
- 一、多线程介绍
- 二、Join 和Sleep
- 三、线程怎样工作
- 四、线程和进程
- 五、线程的使用和误用
一、多线程介绍
C#通过多线程支持并行执行的代码。一个线程是一个独立执行的路径,可以同时与其他线程一起运行。一个C#客户端程序(Console,WPF,Winows Forms)开始于一个单独的线程,该线程由CLR和操作系统自动地创建,我们称它为主线程,而且可以通过创建附加的线程来实现多线程。
所有的例子都假设引入了以下的namespaces:
Using System;
Using System.Threading;
1.初探
1 class Program2 {3 static void Main(string[] args)4 {5 Thread thread = new Thread(WriteY);//创建一个线程6 thread.Start();//开始一个线程7 8 for (int i = 0; i < 1000; i++)//主线程执行循环9 {
10 Console.Write("x");
11 }
12
13 Console.ReadLine();
14 }
15 static void WriteY()
16 {
17 for (int i = 0; i < 1000; i++)
18 {
19 Console.Write("y");
20 }
21 }
22
23 }
一旦开始,一个线程的IsAlive属性返回true,直到这个线程结束。当传递给Thread构造函数的委托完成执行时,这个线程结束。一旦结束,这个线程不能重启。
2.内存隔离
CLR给每个线程分配自己内存栈,因此局部变量可以保持分离。在下一个例子中,我们定义了一个
使用局部变量的方法,然后在主线程和子线程同时调用这个方法。
1 class Program2 {3 static void Main(string[] args)4 {5 new Thread(Go).Start();6 Go();7 Console.ReadKey();8 }9
10 static void Go()
11 {
12 for (int i = 0; i < 5; i++)
13 {
14 Console.Write("y");
15 }
16 }
17 }
因为每个线程的内存栈都有一份隔离的循环变量的拷贝,因此可以推断出,输出结果是10个“y”字符 。
3.数据共享
如果多个线程对同一个对象实例有相同的引用,这些线程就共享这个对象实例的数据。例如:
1 class Program2 {3 bool done = false;4 static void Main(string[] args)5 {6 Program p= new Program();7 new Thread(p.Go).Start();8 p.Go();9 Console.ReadKey();
10 }
11
12 void Go()
13 {
14 if (!done)
15 {
16 done = true;
17 Console.WriteLine("Done");
18 }
19 }
20 }
因为两个线程都调用实例p的go的方法,因此他们共享done这个字段,结果是done只打印出一次而不是两次。
静态字段提供另外一种共享数据的方法:
1 class ThreadTest 2 {3 static bool done; // Static fields are shared between all threads4 5 static void Main()6 {7 new Thread (Go).Start();8 Go();9 }
10
11 static void Go()
12 {
13 if (!done) { done = true; Console.WriteLine ("Done"); }
14 }
15 }
4.线程安全
这两个例子展示了另外一个重要的概念:线程安全确实是不确定的:done可能被打印出两次(尽管是不太可能发生的)。当我们把Go方法中的语句的顺序交换下,打印出两次done的几率显著提升。
1 class Program2 {3 static bool done = false;4 static void Main(string[] args)5 {6 Program p = new Program();7 new Thread(p.Go).Start();8 p.Go();9 Console.ReadKey();
10 }
11
12 void Go()
13 {
14 if (!done)
15 {
16 Console.WriteLine("Done");
17 done = true;
18 }
19 }
20 }
这个地方的问题是线程A在线程B设置done等于true之前进入if条件判断中,所有A有机会打印出"Done"。
改进方式当读\写一个公共字段时,获取一个独占锁(exclusive lock)。C#提供了关键字lock。
1 class Program2 {3 static bool done = false;4 static readonly object locker = new object();5 static void Main(string[] args)6 {7 new Thread(Go).Start();8 Go();9 Console.ReadKey();
10 }
11
12 static void Go()
13 {
14 lock (locker)
15 {
16 if (!done)
17 {
18 Console.WriteLine("Done");
19 done = true;
20 }
21 }
22 }
23 }
当两个线程同时抢占一个锁时(在这个例子中,locker),一个线程等待,或者阻塞,知道这个锁释放。在这个例子中,这个锁保证一次只有一个线程可以进入代码的临界区域,然后“Done”只会被打印一次。代码在这种不确定的多线程背景下中被保护被叫做线程安全。
注意:在多线程中,共享数据是造成复杂原因的主要,而且会产生让人费解的错误。尽管很基本但还是要尽可能保持简单。
一个线程,当阻塞的时候,不占用CPU资源。
回到顶部
二、Join 和Sleep
1.Join
通过调用一个线程的Join方法,可以等待另外一个线程结束。例如:
1 static void Main(string[] args)2 {3 Thread t = new Thread(Go);4 t.Start();5 t.Join();6 Console.WriteLine("Thread t has ended!");7 Console.ReadKey();8 9 }
10 static void Go()
11 {
12 for (int i = 0; i < 1000; i++)
13 {
14 Console.Write("y");
15 }
16 }
这个会打印字符"y"1000次,然后紧接着立刻打印"Thread t has ended!"。Join有多个重载方法,可以在Join方法中添加一个参数,milliseconds或者timeSpan。如果这个线程结束了则Join方法返回true,如果这个线程超时则返回false。
2.Sleep
Thread.Sleep暂停当前线程一段指定的时间:
Thread.Sleep(TimeSpan.FromHours(1));//sleep一个小时
Thread.Sleep(500);//sleep 500 微秒
当使用Sleep或Join暂停线程时,这个线程是阻塞的,不消耗CPU资源。
Thread.Sleep(0)立即放弃这个线程的时间片,主动交出CPU给其他线程。Framework 4.0的新方法Thread.Yield()方法做同样的事,除了当它仅仅在同一个进程中时,才会放弃时间片。
Sleep(0)或Yield()有时候对提升产品性能有用。而且它们也是诊断工具可以帮助揭开线程安全的问题;
如果在代码中的任何地方都插入Thread.Yield(),会造成bug。
回到顶部
三、线程怎样工作
1.多线程由一个线程调度器来进行内部管理,一个功能是CLR常常委托给操做系统。
一个线程调度器确保所有激活的线程在执行期间被合适的分配,等待或者阻塞的线程(比如,一个独占锁或者等待用户输入)不占用CPU资源。
2.在单核电脑上,一个线程调度器让时间片在每一个激活的线程中切换。在windows操作系统下,线程切换的时间分片通常为10微秒,远远大于CPU的开销时间(通常小于1微秒)。
3.在一个多核的电脑上,多线程实现了一个混合的时间片和真正的并发,不同的线程同时在不同的CPU上执行代码。还是存在某些时间片,因为操作系统需要服务它自己的线程,包括其他的应用的线程。
4.当一个线程的执行被内部因素打断,比如时间片,则说这个线程是抢占式的。在大部分情形下,一个线程不能控制自己何时何地被抢占。
回到顶部
四、线程和进程
一个线程类似于你的应用程序正在运行的一个操作系统进程。类似于进程并行运行在一台电脑上,线程并行运行在一个单独的进程中。进程之间是完全隔离的;线程在一定程度上隔离。运行在同一个应用程序下的线程共享堆内存。在某种程度上,这就是为什么线程如此有用:一个线程可以在后台取回数据,比如同时另外一个线程正在显示数据。
回到顶部
五、线程的使用和误用
多线程有许多用途,下面是最通用的:
保持一个可响应的用户界面
通过在一个并行的“worker”线程上运行时间消耗的任务,主UI线程可以空闲地执行键盘或鼠标事件。
使其他阻塞CPU的线程得到最有效的使用
当一个线程正等待另外一计算机或硬件的响应时是非常有用的。当一个线程执行任务时阻塞了,其他线程正好可以使用计算机。
并行编程
如果工作负荷被共享给正在执行“各个击破”策略的多个线程,则代码在多核或多进程中集中计算可以执行得更快。
预测执行
在多核的机器上,你有时通过预测某些事情需要做,然后提前做,从而可以提高性能。LINQPad使用这项技术提高查询的创建。一个变体是运行许多并行的算法去处理同样的任务。无论哪个完成了第一个“wins”-当你预先不知道哪一个算法执行得更快时,这是非常有效的。
允许同时执行请求
在一个server上,客户端请求可以并行抵达,所以需要并行处理。如果你使用ASP.NET,WCF,Web Service或Remoting,.NET Framework 会自动创建线程。这个在client上也是有用的(比如说处理点对点的net working,或者是user的多个请求)。
比如ASP.NET和WCF技术,你可能甚至不会注意到,除非你访问没有合适的locking,违反线程安全的共享数据(假定通过静态字段)。
多线程会带来一系列问题。最大的问题是多线程会提升复杂性。有许多线程本身不会带来复杂性,而是因为线程之间的相互影响(尤其是通过共享数据)。这个适用于是否这个相互影响是故意的,而且这个可以造成长时间的开发周期和一个持续性的敏感性和不可重现的bug。因为这个原因,需要将相互影响降到最低。尽可能坚持和提高可靠的设计。这篇文章主要集中在处理这些复杂性,移除相互影响这个不用多说。
一个好的策略是封装多线程的logic到可复用的类中,这些类可以独立地被测试。这个Framework它自己提供了许多的高级线程构造函数,我们后面再介绍。
线程在调度和切换线程时会造成资源和CPU的消耗(当激活的线程数量多余CPU的核的数量时)-而且有创建/销毁损耗。多线程通常会提升应用程序的速度-但是如果过度或者不适当使用甚至会使应用程序变慢。比如,当硬件I/O被涉及到时,有两个线程串行运行任务比起10个并行线程一次性执行更快。(在等待和脉冲信号中,我们描述怎样实现一个生产者/消费者队列来实现这个功能。)
参考资料:《C# 4.0 in a Nutshell》
C#多线程之旅(1)——介绍和基本概念相关推荐
- C#多线程之旅(4)——APM初探
阅读目录 一.简单的串行执行程序 二.使用委托来实现APM 源码地址:https://github.com/Jackson0714/Threads C#多线程之旅(4)--APM初探 v博客前言 先交 ...
- C#多线程之旅(3)——线程池
阅读目录 代码下载 一.介绍 二.通过TPL进入线程池 三.不用TPL进入到线程池 v博客前言 先交代下背景,写<C#多线程之旅>这个系列文章主要是因为以下几个原因:1.多线程在C/S和B ...
- 多线程之旅之四——浅谈内存模型和用户态同步机制
用户态下有两种同步结构的 volatile construct: 在简单数据类型上原子性的读或者写操作 interlocked construct:在简单数据类型上原子性的读和写操作 (在这里还 ...
- linux 多线程 写日志,rsyslog多线程远程日志记录介绍(lamp+rsyslog)
rsyslog多线程远程日志记录介绍(lamp+rsyslog) rsyslog: rsyslog: 多线程: 支持UDP, TCP, SSL, TLS, RELP远程日志记录 rsyslog支持将日 ...
- C#多线程之旅(七)——终止线程
阅读目录 一.什么时候用Thread.Abort(); 二.Thread.Abort的用法 三.无法终止线程的情形 四.Catch块中抛出异常 五.Finally块中抛出异常 六.Abort调用的时间 ...
- 【我的OpenGL学习进阶之旅】介绍一下 绘制图元
目录 一.绘制图元 1.1 `glDrawArrays` 1.1.1 `glDrawArrays`API说明 1.1.2 `glDrawArrays`API示例 1.2 `glDrawElements ...
- 多线程技术(全面介绍)
目录 1.线程与进程 2.什么是多线程 3.多线程的实现 4.设置和获取线程名称 5.线程的休眠:正在执行的线程休眠(暂时停止执行) 6.线程阻塞 7.线程中断 8.守护线程 9.线程不安全问题 10 ...
- 多线程之旅(10)_QueueUserWorkItem和UnsafeQueueUserWorkItem的区别
这是个比较冷门的点,是我在写多线程之旅(2)_创建一个属于自己的精简线程池_线程调度策略--附C#源码这篇文章时,发现在做线程队列时,官方选用的是UnsafeQueueUserWorkItem,而不是 ...
- ESP32开发之旅——MicroPython介绍
ESP32开发之旅--MicroPython介绍 什么是ESP32 为什么使用MicroPython开发ESP32 参考文献链接 什么是ESP32 ESP32是由我国乐鑫公司继ESP8266芯片后推出 ...
最新文章
- 项目服务器有15个能说明什么,15.1 我的面试经历 by smyhvae - 前端入门进阶
- 深入剖析js命名空间函数namespace
- tracert路由检测命令使用方法
- 龙芯3A5000初样顺利交付流片
- Android之屏幕旋转之后当前activity被finish了依然被拉起来
- 前端学习(2553):内容概述
- 红米k30 android版本,Redmi K30 Pro 推送 MIUI 12.2.1 稳定版:为安卓跨版本升级
- 母函数——找单词(hdu2082)
- sql between包括两端吗_技术分享:T-SQL 之语法艺术(一)
- 企业应用超级App来啦!
- Atitit.实现反向代理(1)----url rewrite 配置and内容改写 and -绝对路径链接改写 java php...
- mysql mysqld.log_MySQL mysqlbinlog 读取mysql-bin文件出错
- 【To Do!】程序员面试金典——11.8维护x的秩
- react - next.js 引用本地图片和css文件
- vector容器——插入和删除
- 10款Mac上程序员装机必备的开发工具推荐和下载
- c语言实现通讯录(详解)
- 【转】你所知道与不知道的游戏机发展史 60-90年代
- 基于Python的招聘网站招聘信息分析
- 人工智能实验报告 牧师与野人渡河 知识表示方法
热门文章
- 《我的十年图像生涯》—王郑耀(西安交通大学)
- 【OpenCV函数】轮廓提取;轮廓绘制;轮廓面积;外接矩形
- 使用EFI引导从硬盘(U盘)安装Win7的图文教程
- Text段、Data段和BSS段
- 计算机工程与应用单像素成像,2011计算机工程与应用基于压缩感知理论的单像素成像系统研究_白凌云.pdf...
- android 广播唤醒应用,Android通过广播实现灭屏和唤醒
- python打开csv文件、计算总成绩_实现读取csv文件,文件里面是有限个百分数成绩(99.6、76.8等等...
- java 异步socket_java Socket读写异步分离
- fir fpga 不同截止频率_学习FPGA将来的出路在哪里?
- awr报告分析 mysql_AWR报告的生成和简单分析方法