代码改变世界

对话线程

2018-06-23 15:41  李明成  阅读(503)  评论(0编辑  收藏  举报

大部分程序员对线程这个概念印象深刻,不是在项目中用到了线程,而是来自面试官的对话。

线程和进程的区别是什么?你可以回忆下你当时的答案,可以在评论去写出来大家共享自己的理解。

进程

运行中的程序实例就叫进程;也就是说一个程序加载到内存后就变成了进程。

进程空间也称为地址空间。地址空间就是进程要用的所有资源。地址空间的特点自己不能做什么,只提供支持。打个比方。看过演出吗?话剧,歌剧...有个舞台,那些道具和舞台就是地址空间。这些空间本省不能发生任何动作,做动作的只能是演员。而这些演员就是我们将要讲述的线程。

进程的缺点

  • 在一个时间内只能干一件事情,如果想同时干两件或多件事情,进程就不够用了。比如:有两场电影,你想同时看,并且在不同的房间内。
  • 如果进程在执行过程中发生阻塞,例如等待输入,整个进程将挂起(暂停),而无法继续执行。这样,即使进程里面有部分工作不依赖于输入数据,也无法进行。为了解决这两个个问题,人们就发明了线程。

进程延伸阅读。

最早的操作系统人要等计算机,计算机也要等人的,那个龟速,可以想象;在到批处理的操作系统,就是解决计算机总是在等待人的下一步动作,采用先想好自己要运行的命令,列成一个清单,打印在纸带上,然后交给计算机管理员来一批一批地处理最后在把用户程序运行结果打印出来交给各个用户。。。。。感兴趣的可以看下操作系统相关书籍

线程

我们知道,进程是运转的程序,是为了在cpu上实现多道编程而发明的一个概念。而如果让进程同时干多件事,办法只能是线程。一个进程中可以有多个线程,那么进程中的资源,线程也可以共享,也有线程本身所独显的资源。如图

  

 

上下文切换

 在一个抢先式多任务系统中,操作系统小心地确保每一个线程都有机会执行。他依赖硬件的协助以及许多的簿记工作。当硬件计时器认为某个线程已经执行够久了,就会发出一个中断,于是CPU取得目前这个线程的当前状态,即把所有寄存器的内容拷贝到堆栈之中,再把它从堆栈拷贝到一个CONTEXT结构(这样便储存了线程的状态)中,以备以后在用。

 要切换不谈的线程,操作系统应先切换该线程所隶属的进程的内存(即page directory 和 page tables),然后恢复该线程放在CONTEXT结构中的寄存器值,这整个过程便称为上线文切换

 

创建线程

    线程需要知道启动运行什么代码,所以它的构造器获取对要执行代码的一个委托。Thread 类包含需要使用 ThreadStart (不接收参数)委托或 ParameterizedThreadStart (接收参数)委托的构造函数;委托包装在调用 Start 方法时由新线程调用的方法。下面demo使用ParameterizedThreadStart 委托,然后调用Strart函数传入参数555,启动线程。新线程运行时,主线程在控制台上打印555个‘|’。

 

 ParameterizedThreadStart tsPara = num =>
            {
                var count = (int)num;
                for (var i = 0; i < count; i++)
                {
                    Console.ForegroundColor = ConsoleColor.DarkGreen;
                    Console.Write("-");
                }
            };
            Thread thread2 = new Thread(tsPara); //带参数
            thread2.Start(555);
           
            for (int i=0;i< 555; i++)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("|");
            }

输出1-1

 

    如输出所示,线程时轮流执行,分别打印x个字符,然后发生上下文切换。两个循环并行运行,而不是一个运行完了第二个才开始。

线程池

 创建线程和销毁线程是一个昂贵的操作,要耗费大量的时间。由于操作系统必须调度可运行的线程并执行上线文切换,所以太多的线程还对性能不利。为了改善这个情况,clr包含了代码来管理他自己的线程池。线程池是你的应用程序能使用的线程集合。线程池内部会维护一个 操作请求队列。应用程序执行一个异步请求操作时,将一个记录项(entry)追加到线程池的队列中。线程池的代码从这个对立中提取记录项,将这个记录项派发(dispatch)给一个线程池线程。当线程池完成任务后,线程不会被销毁。相反,线程会回到线程池,在哪里进入空闲状态,等待相应另一个请求。由于线程不销毁自身,所以不在再产生额外的性能损失;

使用ThreadPool完成上面效果

WaitCallback callback = num =>
            {
                var count = (int)num;
                for (var i = 0; i < count; i++)
                {
                    Console.ForegroundColor = ConsoleColor.DarkGreen;
                    Console.Write("-");
                }

            };

            ThreadPool.QueueUserWorkItem(callback, 555);

            for (int i = 0; i < 555; i++)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("|");
            }

程序结果和上面线程demo输出类似,都是‘-’和 ‘|’字符混合。如果多个不同的作业异步执行,线程池能在单处理器和多处理器计算机上获取更好的执行效率。效率是通过重用线程来获得的。

线程池能很好的完成作业,但该作业中不可包括处理长时间运行的作业或者I/O受限的工作。否则正在排队的工作必然会延迟。

其实呢.NET4.0引入了任务并行库TPL。在4.5对该API进行了轻微的改进,使用更简单。完全可以不用线程或者线程池这些技术,但是基础的知识点还是要了解下的。TPL可被认为是在线程池之上又一个抽象层,其对程序员隐藏了与线程池交互的底层代码。。。下篇一起探讨下。