背景
发现在.Net领域领域中,多线程的处理大致经历了这么几个阶段:
Thread→ThreadPool→委托的异步调用→Task→TaskFactory→Parallerl→异步编程模型(async和await)。
线程、进程、多线程
1.什么是线程?线程和进程的区别是什么?
进程是操作系统对一个正在运行的程序的抽象。操作系统会记录每一个进程的状态,这些状态就称作进程的上下文。这些状态主要包括了PC,寄存器以及主存的当前内容。
当操作系统在进程间切换的时候,也会切换相应的上下文,从而保证进程恢复到之前的状态。
在进程当中,又被计算机界的大神们引入了线程的概念,这些线程可以共享进程级的代码与数据,这种共享一般比进程间的共享更加高效。
线程是程序执行的最小单元。
区别: 进程是操作系统进行资源处理和分配的最小单位,而一个进程可以包含多个线程,并共享进程的资源。
2.什么是多线程?为什么设计多线程?
多线程指的是一个进程可以包含多个并发的线程(同一个时刻只有一个线程运行)。例如酷狗,我们可以一边听歌一边搜索自己喜欢的歌曲。
多线程的存在能够让进程及时处理我们多项的请求,提高应用程序的利用率。
多线程编程需要了解到多线程运行面临的问题:
1)既然一个进程的多个线程共享进程的资源,怎样保证有多个线程访问同一资源时单个线程的访问不受其它线程的干扰。这是线程安全问题。
2)多线程怎么控制线程的执行顺序,这是线程调度问题。
3.什么是主线程
每一个Windows进程都恰好包含一个用作程序入口点的主线程。进程的入口点创建的第一个线程被称为主线程。.Net执行程序(控制台、Windows Form、Wpf等)使用Main()方法作为程序入口点。当调用该方法时,主线程被创建。
4.什么是工作者线程
由主线程创建的线程,可以称为工作者线程,用来去执行某项具体的任务。
5.什么是前台线程
默认情况下,使用Thread.Start()方法创建的线程都是前台线程。前台线程能阻止应用程序的终结,只有所有的前台线程执行完毕,CLR才能关闭应用程序(即卸载承载的应用程序域)。前台线程也属于工作者线程。
6.什么是后台线程
后台线程不会影响应用程序的终结,当所有前台线程执行完毕后,后台线程无论是否执行完毕,都会被终结。一般后台线程用来做些无关紧要的任务(比如邮箱每隔一段时间就去检查下邮件,天气应用每隔一段时间去更新天气)。后台线程也属于工作者线程。
并发与并行的区别
Erlang 之父 Joe Armstrong 用一张5岁小孩都能看懂的图解释了并发与并行的区别
并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人便秘了去厕所呆半天,
后面的人也只能死等着他回来才能去接咖啡,这效率无疑是最低的。
并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。
行话解释:
并发:不同代码块交替执行的性能
并行:不同代码块同时执行的性能
System.Threading
System.Threading命名空间下的Thread类提供了线程的基本操作。 通过创建一个Thread对象,并执行它的Start()方法,可以新建并运行一个新的线程。
后台线程不会影响应用程序的终结,当所有前台线程执行完毕后,后台线程无论是否执行完毕,都会被终结。一般后台线程用来做些无关紧要的任务(比如邮箱每隔一段时间就去检查下邮件,天气应用每隔一段时间去更新天气)。后台线程也属于工作者线程。
1 //主线程入口 2 static void Main(string[] args) 3 { 4 Console.WriteLine("主线程开始!"); 5 6 //创建前台工作线程 7 Thread t1 = new Thread(Task1); 8 t1.Start(); 9 10 //创建后台工作线程 11 Thread t2= new Thread(new ParameterizedThreadStart(Task2)); 12 t2.IsBackground = true;//设置为后台线程 13 t2.Start("传参"); 14 } 15 16 private static void Task1() 17 { 18 Thread.Sleep(1000);//模拟耗时操作,睡眠1s 19 Console.WriteLine("前台线程被调用!"); 20 } 21 22 private static void Task2(object data) 23 { 24 Thread.Sleep(2000);//模拟耗时操作,睡眠2s 25 Console.WriteLine("后台线程被调用!" + data); 26 }
执行发现,【后台线程被调用】将不会显示。因为当所有的前台线程执行完毕后,应用程序就关闭了,不会等待所有的后台线程执行完毕,所以不会显示。
ThreadPool(线程池)
线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率,这也是线程池的主要好处。
ThreadPool适用于并发运行若干个任务且运行时间不长且互不干扰的场景。
还有一点需要注意,通过线程池创建的任务是后台任务。
1 //主线程入口 2 static void Main(string[] args) 3 { 4 Console.WriteLine("主线程开始!"); 5 //创建要执行的任务 6 WaitCallback workItem = state => Console.WriteLine("当前线程Id为:" + Thread.CurrentThread.ManagedThreadId); 7 8 //重复调用10次 9 for (int i = 0; i < 10; i++) 10 { 11 ThreadPool.QueueUserWorkItem(workItem); 12 } 13 Console.ReadLine(); 14 }
System.Threading.Tasks
Task类是封装的一个任务类,内部使用的是ThreadPool类。
.Net 4.0引入了System.Threading.Tasks,简化了我们进行异步编程的方式,而不用直接与线程和线程池打交道。
System.Threading.Tasks中的类型被称为任务并行库(TPL)。TPL使用CLR线程池(说明使用TPL创建的线程都是后台线程)自动将应用程序的工作动态分配到可用的CPU中。
1.Parallel并行编程可以让我们使用极致的使用CPU。并行编程与多线程编程不同,多线程编程无论怎样开启线程,也是在同一个CPU上切换时间片。而并行编程则是多CPU核心同时工作。耗时的CPU计算操作选择并行是明智的。通常情况,每个CPU核心代表一个硬件线程,但超线程技术,可以使一个cpu核心具有两个硬件线程。软件线程顾名思义就是我们在程序中所开启的。
能用Parallel.For的地方就不要用Parallel.ForEach
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 List<Action> actions = new List<Action>() { Credit, Email }; 6 7 var result = Parallel.For(0, actions.Count, (i) => 8 { 9 actions[i](); 10 }); 11 12 Console.WriteLine("执行状态:" + result.IsCompleted); 13 14 Console.Read(); 15 } 16 17 static void Credit() 18 { 19 Console.WriteLine("****************** 发起信用卡扣款中 ******************"); 20 21 Thread.Sleep(2000); 22 23 Console.WriteLine("扣款成功!"); 24 } 25 26 static void Email() 27 { 28 Console.WriteLine("****************** 发送邮件确认单!*****************"); 29 30 Thread.Sleep(3000); 31 32 Console.WriteLine("email发送成功!"); 33 } 34 }
2.PLINQ(并行LINQ查询)
为并行运行而设计的LINQ查询为PLINQ。System.Linq命名空间的ParallelEnumerable中包含了一些扩展方法来支持PINQ查询。
1 int[] modThreeIsZero = (from num in source.AsParallel() 2 where num % 3 == 0 3 orderby num descending 4 select num).ToArray();
3.Task
Task,字面义,任务。使用Task类可以轻松地在次线程中调用方法。
1 static void Main(string[] args) 2 { 3 Console.WriteLine("主线程ID:" + Thread.CurrentThread.ManagedThreadId); 4 Task.Factory.StartNew(() => Console.WriteLine("Task对应线程ID:" + Thread.CurrentThread.ManagedThreadId)); 5 Console.ReadLine(); 6 }
3.1Task.Factory.StartNew无参
Task.Factory.StartNew(() => { MessageBox.Show("测试StartNew:无参数"); });
3.2Task.Factory.StartNew一个参数
private void button5_Click(object sender, EventArgs e) { int val = 5; Task.Factory.StartNew(a => { MessageBox.Show("测试StartNew:参数值" + (int)a); }, val); }
3.3Task.Factory.StartNew多个参数
string userCode=“1000”; string mobile=“13965215964”; Task.Factory.StartNew((p) => { var param = (dynamic)p; //param.userCode //param.mobile }, new { userCode = userCode, mobile = mobile });
4.泛型Task
Task是Task的泛型版本,可以接收一个返回值。
1 static void Main(string[] args) 2 { 3 Console.WriteLine("主线程ID:" + Thread.CurrentThread.ManagedThreadId); 4 Task<string> task = Task.Run(() => 5 { 6 return Thread.CurrentThread.ManagedThreadId.ToString(); 7 }); 8 Console.WriteLine("创建Task对应的线程ID:" + task.Result); 9 10 Console.ReadLine(); 11 }
5.Task.Start()和Task.Factory.StartNew()之间有什么区别?
实战总结
本文主要梳理了以下几点:
任务Task与并行Parallel本质上内部都是使用的线程池,提供了更丰富的并行编程的方式。
默认创建的Thread是前台线程,创建的Task为后台线程。
ThreadPool创建的线程都是后台线程。
任务并行库(TPL)使用的是线程池技术。
class Program { static void Main(string[] args) { string codeStr = "D050,B023,E059,B020,E067,A011,B024,I137,E066,B014,A006,C042,A002,D047,D046,C029"; IList<string> resultList = codeStr.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); int everyCount = 4;//4个一组 int total = resultList.Count;//总数 int countThread = (int)Math.Ceiling((double)total / everyCount);//线程个数 IList<List<string>> listTotal = new List<List<string>>(); for (int i = 0; i < countThread; i++) { List<string> list = new List<string>(); int ct = i * everyCount; for (int j = ct; j < ct + everyCount; j++) { if (j < resultList.Count) { string res = resultList[j]; list.Add(res); } } listTotal.Add(list); } //第一种多线程调用方式 Parallel.For(0, listTotal.Count, (i) => { Console.WriteLine("数组索引{0}对应的那个元素{1}", i, listTotal[i]); DealData(listTotal[i]); }); //第二种多线程调用方式 Parallel.ForEach(listTotal, (item) => { DealData(item); }); //或者 ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount}; Parallel.ForEach(listTotal,parallelOptions,(item) => { DealData(item); }); //第三种多线程调用方式 Thread[] array = new Thread[countThread]; for (int i = 0; i < array.Length; i++) { ParameterizedThreadStart ParStart1 = new ParameterizedThreadStart(DealData); array[i] = new Thread(ParStart1); List<string> list = listTotal[i]; array[i].Start(list); } for (int i = 0; i < array.Length; i++) { array[i].Join(); } //第四种多线程调用方式 Task[] tks = new Task[countThread]; for (int i = 0; i < listTotal.Count; i++) { tks[i] = new Task(DealData, listTotal[i]); tks[i].Start(); } Task.WaitAll(tks); //第五种多线程调用方式 var listTask = new List<Task>();//存储所有线程任务 foreach (var item in listTotal)//几个细分任务就创建几个线程 { listTask.Add(Task.Factory.StartNew(() => DealKbaseData(item)));//处理单个线程 //listTask.Add(Task.Factory.StartNew(() => Console.WriteLine("此处直接写逻辑处理代码,则无需考虑因方法封装而带来的参数传递问题"))); } Task.WaitAll(listTask.ToArray());//等待所有线程处理完毕! Console.ReadLine(); } /// <summary> /// 输出 /// </summary> /// <param name="result"></param> private static void DealData(object result) { foreach (string item in (IList<string>)result) { Console.WriteLine(item); } } }
Parallel
方式一 Parallel.Invoke(() => Task1(), () => Task2(), () => Task3()); 方式二 Parallel.Invoke(Task1, Task2, Task3); 方式三 Parallel.Invoke( () => { Task1(); }, Task2, delegate () { Task3(); console.write('do someting!');});
收集资料
http://www.cnblogs.com/afei-24/p/6904179.html
https://www.cnblogs.com/jonins/p/9444374.html