C#多线程(一) 入门
本文你会了解如下内容:
1、计算机程序、进程、线程的概念
2、多线程的概念、为什么需要多线程、多线程的好处与坏处
3、C# 线程的一些概念与操作(创建线程、像线程中传递参数、给线程取名、前后台线程、线程优先级、异常处理)
4、线程池
一、计算机程序、进程、线程的概念
计算机程序:是计算机能识别和处理的指令集合,通常情况下是计算机操作系统可以执行的二进制文件,如在Win32系统上符合PE(Portable Executable)格式的二进制文件就可算是计算机程序。
进程:是计算机程序在计算机操作系统中运行的产物。一个计算机程序可以有多个进程,如一个游戏程序运行多次就会得到多个游戏程序的进程。
注:在早期的操作系统(如早期的 Unix)是基于进程设计的,进程既是掌管资源的单位又是执行的单位;在现代的操作系统中,进程本身不是执行的单位,而是线程的容器,此时的进程仅仅是掌管资源的单位。
线程:是一个独立的执行流,每个线程有自己独立的线程栈。在同一进程下的线程共享进程的资源。
二、多线程的概念、为什么需要多线程、多线程的好处与坏处
多线程:通过软件或硬件的技术实现多个线程并发执行的技术,在通常情况下就是指一个进程中有多个线程(并发执行)的现象。
我们知道在现在主流计算机体系结构(冯诺依曼体系机构)中CPU是很宝贵的资源,我们要尽可能的提高CPU的利用率。在日常的任务操作中,有大量任务是与IO设备交互,应为IO设备的运行速度比CPU的运行速度慢好几个数量级,在需要大量IO操作的任务或程序(IO密集型)中CPU会有一段不短的时间等待IO设备处理完毕,这就造成了CPU资源的浪费。为了解决这种浪费,为了更充分的利用CPU资源,我们引入多线程技术。针对刚才的例子,我们可以通过把需要和IO设备交互的工作交给一个线程(通常是后台线程)处理,主线程继续执行其他任务,这样就解决问题了,这也就是为什么需要需要多线程技术。
多线程的好处:
1、提高了CPU的利用率
2、提升了程序的效率
多线程的坏处:
1、增加了程序的复杂度(创建线程不复杂、复杂的是线程间的协调工作),可能导致一些很难调试的BUG
2、比单线程程序消耗更多的资源(每个线程有独立的线程栈,这是需要资源滴)
建议:
1、将多线程逻辑封装到一个可重用的类库中,这个类库可以被充分的测试。最好不要将多线程处理逻辑和业务处理逻辑耦合得太紧,这样做是在给自己挖坑 
三、C# 线程的一些概念与操作
光说不练假把式,先来看个例子(为了节省文章版幅,因此代码有时候采用缩行处理)
例1:创建线程
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(WriteY);
t.Start();
while(true) {Console.Write("x");}
} static void WriteY()
{
while(true) {Console.Write("y");}
}
}
编译: csc 1.cs
运行 1.exe > result.txt
上面的 “>” 是一个DOS运算符,含义是将 1.exe 打印的字符写入到 result.txt 文本中
效果:

引用 Thread in C# 中的一幅图

刚才我们说每个线程有独立的线程栈,我们写代码验证下
using System;
using System.Threading; // 每个线程内的变量互相不影响,CLR为每个线程在堆上分配独立的线程栈控件
class App
{
static void Main()
{
new Thread(Go).Start();
Go();
} static void Go()
{
for(int cycles = ; cycles<; cycles++) {Console.Write("?");}
}
}
运行效果:??????????
10 个问号,不是5个问号,正好验证了我们的猜测。
当线程引用公有变量(全局变量)时它们会共享此公有变量的数据,看下例:
using System;
using System.Threading;
// 实例字段和实例方法
class App
{
bool done;
static void Main()
{
App app = new App();
new Thread(app.Go).Start();
app.Go();
} void Go()
{
if(!done){done = true; Console.WriteLine("This Only Print Once");}
}
}
运行效果: This Only Print Once
貌似这个程序得到了我们要的效果,其实这样做是存在打印出两次的风险的(虽然概率极小),为了让出现打印两次的概念变大许多,我们改变以下 done = true; 和Console.WriteLine("This Only Print Once"); 的次序变为:
void Go()
{
if(!done){Console.WriteLine("This Only Print Once");done = true; }
}
运行结果变为:
This Only Print Once
This Only Print Once
打印了两次,这并不是我们想要的结果。通过这个例子,我们引入线程安全的概念(其实这是一个非线程安全的例子)即在多线程情况下,每次运行结果和单线程运行的结果是一样的,相关变量的值也是在预期范围内的,这样的情况成为线程安全,反之称为非线程安全(说白了,线程安全,就是程序跑出的结果和你猜想的一致,非线程安全就是和你想的不一致)
在C#中解决上面非线程安全问题的最简单的方式就是用 lock 语句锁住多线程交互的地方,看代码:
using System;
using System.Threading; class App
{
static bool done;
static object _locker = new object();
static void Main()
{
new Thread(Go).Start();
Go();
} static void Go()
{
lock(_locker)
{
if(!done) {Console.WriteLine("This Only Print Once");done = true;}
}
}
}
运行结果:This Only Print Once
注意:
1、线程间共享公有数据是多线程复杂性以及产生难以发现BUG的主要原因,尽管多线程之间经常需要共享公共数据,但尽量使共享数据交互的代码尽量简单吧 
2、通过lock语句每次只有一个线程进入临界区,其余线程被挡在临界区外(或等待或被阻塞Blocked)直到获取到lock的锁对象。
3、lock 语句是比较耗资源的,lock语句锁住的范围最好尽量的小。考虑一个极端的情况,在一个多线程程序中用lock语句锁住的范围是整个程序的执行范围,这是神马情况?这种情况不就是和单线程执行效果一样了么,那还用毛线多线程技术和lock语句呀,对吧?!
Join 与 Sleep语句
你可以通过Join语句实现线程A等待线程B执行完的功能,看代码:
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(() =>
{
for(int i = ; i< ; i++) {Console.Write("y ");}
});
t.Start();
t.Join(); // 主线程等待 线程t执行完毕再结束
Console.WriteLine("Thread t has ended!");
}
}
编译 :csc 10_join.cs
运行 : 10_join.exe > joinResult.txt
程序执行效果:

其实本例的t.Join(); 加不加效果都一样,原因是用 Thread 创建的线程是前台线程,程序要等到所有前台线程执行完毕后才会退出,这个下面会谈到。
Thread.Sleep 暂停当前线程指定时间
Thread.Sleep(TimeSpan.FromHours()); // 暂停1小时
Thread.Sleep(); // 暂停 500 毫秒
注意:
1、Thread.Sleep(0) 或 Thread.Yield() 表示主动放弃当前线程执行权,把CPU资源让给别的线程。Yield(有放弃、让步、屈服之意)
ThreadStart委托与 ParameterizedThreadStart 委托
先看看MSDN上的定义:
public delegate void ThreadStart() // 无参
public delegate void ParameterizedThreadStart(Object obj) // 带参数,可以向线程中传递数据,但注意这里只能用Object,因此涉及到装箱拆箱操作
初始化委托有四种方法(原始方式、方法方式、匿名方法方式、Lambda 方式)详见 类库探源——System.Delegate
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(new ThreadStart(WriteY)); // 这是原始的方法
/* 还可以
Thread t = new Thread(WriteY); // 方法方式
Thread t = new Thread(delegate(){while(true) {Console.Write("y");}}); // 匿名方法
Thread t = new Thread(() => {while(true) {Console.Write("y");}}); // Lambda 表达式
*/
Console.WriteLine(t.IsBackground); // 用 Thread 创建的线程是前台线程 结果是 False
t.Start();
while(true) {Console.Write("x");}
} static void WriteY()
{
while(true) {Console.Write("y");}
}
}
运行结果:

向线程中传递数据
首先我们可以用 ParameterizedThreadStart 委托传递参数,看代码:
using System;
using System.Threading; class App
{
static void Main()
{
string str = "Contents";
var t = new Thread(Invoke);
t.Start(str); // 传参
}
static void Invoke(object str)
{
Console.WriteLine(str);
}
}
上面的代码可以用 Lambda 表达式写得更精练
static void Main()
{
string str = "Contents";
new Thread((obj) =>{Console.WriteLine(obj);}).Start(str);
}
第二种方法用匿名方法(或Lambda表达式)调用一个普通方法变通解决:
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(delegate(){WriteText("hello Thread Param");});
//可用Lambda表达式 var t = new Thread(() =>WriteText("hello Thread Param"));
t.Start();
}
static void WriteText(string txt)
{
Console.WriteLine(txt);
}
}
Lambda 表达式值捕获问题
如我们所见 Lambda 表达式是向线程传值的最强大的方式,但是你必须小心值捕获(captured variables)问题,也有人把这个问题称为闭包下值的问题,看代码:
using System;
using System.Threading; class App
{
static void Main()
{
for(int i = ; i < ; i++)
{
new Thread(() => Console.Write(i)).Start();
}
}
}
运行几次的效果:

可以看出每次都不一样
,也没有一次值为 0123456789 这是为什么呢?
原因是在循环指向过程中的变量 i 指向了相同的内存位置(i variable refers to the same memory location throughout the loop’s lieftime.)每个线程获取到i的值不会那么巧正好是 0123456789的顺序
解决方法:用个临时变量存储i
int temp = i;
new Thread(() => Console.Write(temp)).Start();
效果:

每次都是 0123456789
再用更简单例子来说明:
string text = "t1";
var t1 = new Thread(() =>Console.WriteLine(text)); string text = "t2";
var t2 = new Thread(() =>Console.WriteLine(text)); t1.Start();
t2.Start();
结果是打印 两个 t2
给线程取名
给线程命名在调试的时候很有用,在VS下可以查看线程名字
Thread.CurrentThread 获取当前正在运行的线程
using System;
using System.Threading; class App
{
static void Main()
{
Thread.CurrentThread.Name = "Main Thread";
var worker = new Thread(Go);
worker.Name = "Worker Thread";
worker.Start(); // 另取线程调用Go
Go(); // 主线程中调用Go
} static void Go()
{
Console.WriteLine("Hello from " + Thread.CurrentThread.Name);
}
}
运行效果:

前台线程与后台线程
用 Thread 类创建的线程默认是前台线程,应用程序的所有前台线程执行完后才结束,如何特殊设置(如Join)前台线程结束后,后台线程也会终止因为程序都terminate 了嘛
。
看下刚才Join的例子,我们将用后台线程演示,看代码:
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(() =>
{
for(int i = ; i< ; i++) {Console.Write("y ");}
});
t.IsBackground = true;
t.Start(); Console.WriteLine("Thread t has ended!");
}
}
运行2次效果:

第一次执行打印一个y 第二次执行打印一个y ,反正都没打印完
现在用 Join 语句阻塞下
using System;
using System.Threading; class App
{
static void Main()
{
Thread t = new Thread(() =>
{
for(int i = ; i< ; i++) {Console.Write("y ");}
});
t.IsBackground = true;
t.Start();
t.Join();
Console.WriteLine("Thread t has ended!");
}
}
效果:

后台线程执行完了。
注意:
1、Thread 创建的线程默认是前台线程
2、线程池创建的线程默认是后台线程
3、线程的前后台状态与线程的优先级没有关系
线程优先级
public enum ThreadPriority{Lowest,BelowNormal,Normal,AboveNormal,Highest}
异常处理
参见 类库探源——System.Exception 中第四和第五小节
未完
本系列参考:Joseph Albaharis Thread in C#
C#多线程(一) 入门的更多相关文章
- 转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可 ...
- Java的多线程 简单入门
Java的多线程 简单入门 首先能够先搞清楚什么是程序.进程.线程,以及它们之间的关系: 定义: 一 程序仅仅是一组指令的有序集合.它是静态的 二 进程是具有一定独立功能的程序关于某个数据集合上的一次 ...
- 微博,and java 多线程编程 入门到精通 将cpu 的那个 张振华
http://down.51cto.com/data/2263476 java 多线程编程 入门到精通 将cpu 的那个 张振华 多个用户可以同时用一个 vhost,但是vhost之间是隔离的. ...
- Java 高级 --- 多线程快速入门
这世上有三样东西是别人抢不走的:一是吃进胃里的食物,二是藏在心中的梦想,三是读进大脑的书 多线程快速入门 1.线程与进程区别 每个正在系统上运行的程序都是一个进程.每个进程包含一到多个线程.线程是一组 ...
- java 多线程 快速入门
------------恢复内容开始------------ java 多线程 快速入门 1. 进程和线程 什么是进程? 进程是正在运行的程序它是线程的集合 进程中一定有一个主线程 一个操作系统可以有 ...
- [转]Delphi多线程编程入门(二)——通过调用API实现多线程
以下是一篇很值得看的关于Delphi多线程编程的文章,内容很全面,建议收藏. 一.入门 ㈠. function CreateThread( lpThreadAttributes: Pointer ...
- java的多线程之入门
一.java多线程基本概念 调用run():在主线程调用子线程的run()方法会中断主线程等到子线程执行完毕之后再执行主线程. 调用start():在主线程中执行子线程的start()后会与主线程同步 ...
- C++多线程编程入门之经典实例
多线程在编程中有相当重要的地位,我们在实际开发时或者找工作面试时总能遇到多线程的问题,对多线程的理解程度从一个侧面反映了程序员的编程水平. 其实C++语言本身并没有提供多线程机制,但Windows系统 ...
- Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)
介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间, ...
- [转]Delphi多线程编程入门(一)
最近Ken在比较系统地学习Delphi多线程编程方面的知识,在网络上查阅了很多资料.现在Ken将对这些资料进行整理和修改,以便收藏和分享.内容基本上是复制粘贴,拼拼凑凑,再加上一些修改而来.各个素材的 ...
随机推荐
- 都说ConcurrentDictionary<TKey, TValue>有陷阱
看这么几句解释(英文原帖): private static void ConcurrentDictionary() { var dict = new ConcurrentDictionary<i ...
- public staic void main 总结
jvm 就是java的操作系统.深入了解jvm很必要. public:该函数的修饰符,表示该函数是公有的,无需多言. static 对于函数的修饰,表明该方法为静态方法,可以通过类名直接调用,事项对于 ...
- 2014-5-23 s3c2440到手
( 之前的开发板是s5pv210 (contex A8)); 现在入手JZ2440......................
- oracle 分区表和分区索引
很复杂的样子,自己都没有看完,以备后用 http://hi.baidu.com/jsshm/item/cbfed8491d3863ee1e19bc3e ORACLE分区表.分区索引ORACLE对于分区 ...
- bzoj 2707 [SDOI2012]走迷宫(SCC+高斯消元)
Description Morenan被困在了一个迷宫里.迷宫可以视为N个点M条边的有向图,其中Morenan处于起点S,迷宫的终点设为T.可惜的是,Morenan非常的脑小,他只会从一个点出发随机沿 ...
- iOS开发——Block详解
iOS开发--Block详解 1. Block是什么 代码块 匿名函数 闭包--能够读取其他函数内部变量的函数 函数变量 实现基于指针和函数指针 实现回调的机制 Block是一个非常有特色的语法,它可 ...
- 使用CPU探查器优化XAML程序
如果您正在开发一个使用 XAML (是否是 c + +. C# 或 VB) 的 Windows 商店应用程序,还有一个好的机会来提高应用程序的性能.为了帮助完成这一点,我们所有在售的能够应用开发 Wi ...
- Scala学习笔记(三)类层级和特质
无参方法 功能:将方法的定义转换为属性字段的定义: 作用范围:方法中没有参数,并且方法仅能通过读取所包含的对象属性去访问可变状态,而不改变可变状态,就可使用无参方法: 例子: abstract cla ...
- UVa10635 - Prince and Princess(LCS转LIS)
题目大意 有两个长度分别为p+1和q+1的序列,每个序列中的各个元素互不相同,且都是1~n^2之间的整数.两个序列的第一个元素均为1.求出A和B的最长公共子序列长度. 题解 这个是大白书上的例题,不过 ...
- redis在.NET下的使用
windows SEVER包:http://code.google.com/p/servicestack/wiki/RedisWindowsDownload windows仅用来测试,性能不如在lin ...