线程初步了解 - <第一篇>

操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。

  在Windows操作系统下,线程的的数据结构包含以下内容:

  1、线程的核心对象:主要包含线程当前的寄存器状态,当操作系统调度这个线程开始运行的时候,寄存器的状态将被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,已备下一次执行的时候使用。
  2、线程环境块(Thread Environment Block,TED):是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程的局部存储数据(Thread Local Storage Data)也存在这里。
  3、用户模式的堆栈:用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会被分配1M的空间用于用户模式堆栈。
  4、内核模式堆栈:用于访问操作系统时使用的堆栈。

  在抢先式多任务的环境下,在一个特定的时间,CPU将一个线程调度进CPU中执行,这个线程最多将会运行一个时间片的时间长度,当时间片到期之后,操作系统将这个线程调度出CPU,将另外一个线程调度进CPU,我们通常称这种操作为上下文切换。

  在每一次的上下文切换时,Windows将执行下面的步骤:

  • 将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中。
  • 选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间。
  • 加载准备运行线程的CPU寄存器状态到CPU中。

  公共语言运行时CLR(Common Language Runtime)是.Net程序运行的环境,它负责资源管理,并保证应用和底层操作系统之间必要的分离。

  在.Net环境下,CLR中的线程需要通过操作系统的线程完成实际的工作,目前情况下,.Net直接将CLR中的线程映射到操作系统的线程进行处理和调度,所以,我们每创建一个线程将会消耗1M以上的内存空间。但未来CLR中的线程并不一定与操作系统中的线程完全对应。通过创建CLR环境下的逻辑线程,我们可能创建更加节省资源的线程,使得大量的CLR线程可以工作在少量的操作系统线程之上。

一、线程的定义

  在单CPU系统的一个单位时间(time slice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保持到线程的本地存储器(TLS)中,以便下次执行时恢复执行。而多线程只是系统带来的一个假象,它在多个单位时间内进行多个线程的切换,因为切换频密而且单位时间非常短暂,所以多线程被视作同时运行。

  适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。

  1、System.Threading 命名空间中的常用类

  在System.Threading命名空间内提供多个方法来构建多线程应用程序,其中ThreadPool与Thread是多线程开发中最常用到的,在.NET中专门设定了一个CLR线程池专门用于管理线程的运行,这个CLR线程池正是通过ThreadPool类来管理,而Thread是管理线程的最直接方式。

说明
AutoResetEvent 通知正在等待的线程已发生事件
ManualResetEvent 通知正在等待的线程已发生事件
Interlocked 为多个线程共享的变量提供原子操作
Monitor 提供同步对对象的访问的机制
Mutex 一个同步基元,也可用于进程间同步
Thread 创建并控制线程,设置其优先级并获取其状态
ThreadPool  提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器
WaitHandle 封装等待对共享资源的独占访问的操作系统特定的对象
ReadWriterLock 读写锁
Semaphore 控制线程的访问数量

二、线程的优先级

  为了方便线程的管理,线程有个优先级,优先级用于决定哪个线程优先执行,在Thread对象中就是Priority属性。

  优先级由低到高分别是:

优先级 说明
Lowest 可以将 Thread 安排在具有任何其他优先级的线程之后
BelowNormal 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前
Normal 默认值。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前
AboveNormal 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前
Highest 可以将 Thread 安排在具有任何其他优先级的线程之前

  先来看一个优先级的示例:

    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Lowest;
            Thread t2 = new Thread(Run);
            t2.Priority = ThreadPriority.Normal;
            Thread t3 = new Thread(Run);
            t3.Priority = ThreadPriority.Highest;
            //由低到高优先级的顺序依次调用
            t1.Start();
            t2.Start();
            t3.Start();
            Console.ReadKey();
        }

        public static void Run()
        {
            Console.WriteLine("我的优先级是:" + Thread.CurrentThread.Priority);
        }
    }

  来看输出:

  

  留意到线程是按照优先级的顺序执行的。

三、常用属性

常用属性 说明
CurrentThread 获取当前正在运行的线程
IsAlive 获取一个值,该值指示当前线程的执行状态
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程, 后台线程会随前台线程的关闭而退出
IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池
ManagedThreadId 获取当前托管线程的唯一标识符
Name 获取或设置线程的名称
Priority 获取或设置一个值,该值指示线程的调度优先级
ThreadState 获取一个值,该值包含当前线程的状态

  ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。不能够通过Name,因为Name只是一个简单的属性,可以随便改,不能保证无重复。

  常用属性示例:

    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Normal;
            t1.Start();

            Console.ReadKey();
        }

        public static void Run()
        {
            Thread t1 = Thread.CurrentThread;   //静态属性,获取当前执行这行代码的线程
            Console.WriteLine("我的优先级是:" + t1.Priority);
            Console.WriteLine("我是否还在执行:" + t1.IsAlive);
            Console.WriteLine("是否是后台线程:" + t1.IsBackground);
            Console.WriteLine("是否是线程池线程:" + t1.IsThreadPoolThread);
            Console.WriteLine("线程唯一标识符:" + t1.ManagedThreadId);
            Console.WriteLine("我的名称是:" + t1.Name);
            Console.WriteLine("我的状态是:" + t1.ThreadState);
        }
    }

  输出如下:

  

  1、前台线程与后台线程的区别

  我们看到上面有个属性叫后台线程,非后台线程就叫前台线程吧,Thread.Start()启动的线程默认为前台线程,启动程序时创建的主线程一定是前台线程。应用程序与必须等到所有的前台线程执行完毕才会卸载。而当IsBackground设置为true时,就是后台线程了,当主线程执行完毕后就直接卸载,不再理会后台线程是否执行完毕。

  前台与后台线程的设置必须在线程启动之前进行设置,线程启动之后就不能设置了。

  Thread创建的线程是前台线程,线程池中的是后台线程。

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.IsBackground = true;     //设为后台线程
            t1.Start();
            Console.WriteLine("不等你咯,后台线程!");

            //注意这里不要Console.Readxxx();,让控制台执行完毕就自动关闭
        }

        public static void Run()
        {
            Thread.Sleep(5000);
            Console.WriteLine("后台线程正在执行!");
        }
    }

  前台线程与后台线程的区别如下,上面的示例没法用图片来说明,简要说发生的情况。当t1设置为前台线程时,5秒后,控制台窗口才关闭。如果t1设置为后台线程,则窗口瞬间就关闭了。

  2、ThreadState的状态

  对于ThreadState的值有以下几种:

线程状态 说明
Aborted 线程已停止
AbortRequested 线程的Thread.Abort()方法已被调用,但是线程还未停止
Background 线程在后台执行,与属性Thread.IsBackground有关
Running 线程正在正常运行
Stopped 线程已经被停止
StopRequested 线程正在被要求停止
Suspended 线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
SuspendRequested 线程正在要求被挂起,但是未来得及响应
Unstarted 未调用Thread.Start()开始线程的运行
WaitSleepJoin 线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

  线程在以上几种状态的切换如下:

  刚刚创建的线程处于已经准备好运行,但是还没有运行的状态,称为Ready(准备)状态。在操作系统的调度之下,这个线程可以进入(Runing)运行状态。运行状态的线程可能因为时间片用完的缘故被操作系统切换出CPU,称为Suspended(暂停运行)状态,也可能在时间片还没有用完的情况下,因为等待其他优先级更高的任务,而转换到Blocked(阻塞)状态。在阻塞状态下的线程,随时可以因为再次调度而重新进入运行状态。线程还可能通过Sleep方法进入Sleep(睡眠)状态,当睡眠时间到期之后,可以再次被调度运行。处于运行状态的线程还可能被主动终止执行,直接结束;也可能因为任务已经完成,被操作系统正常结束。

四、方法

方法 说明
Abort 终止线程
GetDomain 当前线程运行在的应用程序域
GetDomainID 唯一的应用程序域标识符
Interrupt 中断处于 WaitSleepJoin 线程状态的线程
Join 阻塞调用线程,直到某个线程终止时为止
ResetAbort 取消为当前线程请求的 Abort
Sleep 将当前线程阻塞指定的毫秒数
SpinWait 导致线程等待由 iterations 参数定义的时间量
Start  启动线程以按计划执行

1、Join串行执行

Join,串行执行,相当于ajax里面的async:false

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.Name = "t1";
            t1.Start();
            t1.Join();      //等待t1执行完之后,主线程再执行,线程间的关系为串行,非并行
            Console.WriteLine("主线程执行这了么?");
            Console.ReadKey();
        }

        public static void Run()
        {
            Console.WriteLine("线程" + Thread.CurrentThread.Name + "开始执行!");
            Thread.Sleep(5000);
            Console.WriteLine("线程" + Thread.CurrentThread.Name + "执行完毕!");
        }
    }

  输出:

  

  2、Interrupt 与 Abort

  Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

1、Interrupt:  抛出的是 ThreadInterruptedException 异常。

     Abort:  抛出的是  ThreadAbortException 异常。

2、Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个contine操作。如果线程正在sleep状态,则通过Interrypt跳过一次此状态也能够达到唤醒效果。

     Abort:这个就是相当于一个break操作,工作线程彻底停止掉。 当然,你也已在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.Start();
            //当Interrup时,线程已进入for循环,中断第一次之后,第二次循环无法再停止
            t1.Interrupt();

            t1.Join();
            Console.WriteLine("============================================================");

            Thread t2 = new Thread(Run);
            t2.Start();
            //停止1秒的目的是为了让线程t2开始,否则t2都没开始就直接中止了
            Thread.Sleep(1000);
            //直接终止掉线程,线程被终止,自然无法输出什么!
            t2.Abort();

            Console.ReadKey();
        }

        static void Run()
        {
            for (int i = 1; i <= 5; i++)
            {
                try
                {
                    //连续睡眠5次
                    Thread.Sleep(2000);
                    Console.WriteLine("第" + i + "次Sleep!");
                }
                catch (Exception e)
                {
                    Console.WriteLine("第" + i + "次Sleep被中断!" + "   " + e.Message);
                }
            }
        }
    }  

  输出:

  

  3、Suspend 与 Resume (慎用)

  Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。在MSDN中,这两个方法也已被标记为已过时。

五、ThreadStart委托

  ThreadStart所生成并不受线程池管理。

  通过ThreadStart委托启动线程:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程Id是:" + Thread.CurrentThread.ManagedThreadId);
            Message message = new Message();
            Thread thread = new Thread(new ThreadStart(message.ShowMessage));
            thread.Start();
            Console.WriteLine("正在做某事......");
            Console.WriteLine("主线程工作完成!");

            Console.ReadKey();
        }

        public class Message
        {
            public void ShowMessage()
            {
                string message = string.Format("异步线程Id是:{0}", Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine(message);
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(300);
                    Console.WriteLine("异步线程当前循环执行到" + i);
                }
            }
        }
    }

  输出:

  

六、ParameterizedThreadStart委托

  ParameterizeThreadStart委托于ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart对应方法的参数为object。

    class Person
    {
        public Person(string name, int age){ this.Name = name;this.Age = age; }
        public string Name { get;  set; }
        public int Age { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //整数作为参数
            for (int i = 0; i < 2; i++)
            {
                Thread t = new Thread(new ParameterizedThreadStart(Run));
                t.Start(i);
            }
            Console.WriteLine("主线程执行完毕!");

            //自定义类型作为参数
            Person p1 = new Person("关羽", 22);
            Person p2 = new Person("张飞", 21);
            Thread t1 = new Thread(new ParameterizedThreadStart(RunP));
            t1.Start(p1);
            Thread t2 = new Thread(new ParameterizedThreadStart(RunP));
            t2.Start(p2);

            Console.ReadKey();
        }

        public static void Run(object i)
        {
            Thread.Sleep(50);
            Console.WriteLine("线程传进来的参数是:" + i.ToString());
        }

        public static void RunP(object o)
        {
            Thread.Sleep(50);
            Person p = o as Person;
            Console.WriteLine(p.Name + p.Age);
        }
    }

  输出:

  

七、TimerCallback委托

  TimerCallback委托专门用于定时器的操作,这个委托允许我们定义一个定时任务,在指定的间隔之后重复调用。实际的类型与ParameterizedThreadStart委托是一样的。

  Timer类的构造函数定义如下:

  public Timmer(TimerCallback callback,Object state,long dueTime,long period)
  • Callback表示一个时间到达时执行的委托,这个委托代表的方法必须符合委托TimerCallback的定义。
  • State表示当调用这个定时器委托时传递的参数。
  • dutTime表示从创建定时器到第一次调用时延迟的时间,以毫秒为单位。
  • Period表示定时器开始之后,每次调用之间的时间间隔,以毫秒为单位。

  示例,使用TimerCallback每隔一秒钟输出一次时间:

    class Program
    {
        static void Main(string[] args)
        {
            System.Threading.Timer clock = new System.Threading.Timer(ConsoleApplication1.Program.ShowTime, null, 0, 1000);

            Console.ReadKey();
        }

        public static void ShowTime(object userData)
        {
            Console.WriteLine(DateTime.Now.ToString());
        }
    }

  输出如下:

  

时间: 2024-12-19 10:19:11

线程初步了解 - <第一篇>的相关文章

对volley的初步分析第一篇

进入android世界已经快要两年了,放眼望去,在这两年的android世界里,自己多少也成长了点,一路上磕磕碰碰,即使现在的我还是android学术界里最垫底的那一位,但还是阻挡不了我对未来android世界的探索的脚步,自从上次我坚定不移的辞掉了我的工作后,我发现这真的是我做得最正确的决定没有之一,我不知道当初我为什么要这么做,但是我只知道我耳边有这么个声音告诉我,世界那么多大,你真的该去看看了,我不想每天就码着烂掉牙的几行代码,一些被自己反复使用的封装的工具类,最可怕的是自己完全没有推陈出

U-BOOT-2016.07移植 (第一篇) 初步分析

U-BOOT-2016.07移植 (第一篇) 初步分析 目录 U-BOOT-201607移植 第一篇 初步分析 目录 编译和移植环境 更新交叉编译工具 1 下载arm-linux-gcc 443 2 安装arm-linux-gcc 443 安装环境Ubuntu 910 下载u-boot-201607并解压 分析顶层Makefile 1 找出目标依赖关系 2 总结 初次编译u-boot 1 配置 2 编译 分析u-boot启动流程 1 分析startS 2 分析crt0S 3 总结 1. 编译和移

第一篇:初步认识C#---------C#笔记

一.什么叫.NET? .NET: 一般指.Net Framework框架.是一种平台,是一种技术. 二.Net Framework框架主要组成部分?    Net Framework框架主要组成: 1>  类库(FCL) 2>  公共语言(CLR) 三..NET程序的简单编译原理?      1 .编写源代码 2. 将源代码编译成微软中间代码(MSIL) 3. 将中间代码交给CLR的即时编译器(JIT) 4. JIT将中间代码转换成平台对应的CPU指令 5. 将CPU指令交给CPU执行 四.托

Matrix源码分析之第一篇

Matrix源码分析之第一篇 概述 前几天腾讯将一款Android应用性能监控的框架matrix开源了,源码地址在https://github.com/Tencent/matrix,作者是微信终端团队.matrix到底是什么?据官方说法如下:Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上. Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集

SaltStack 入门到精通 - 第一篇: 安装SaltStack

实际环境的设定: 系统环境: centos6 或centos5 实验机器: 192.168.1.100 软件需求: salt 套件,及其需求环境 实验目的: 成功安装salt,并实现salt主从间通讯 特殊设置: 其它目的: 安装SaltStack(下面简称为salt) epel安装:salt安装需要epel源支持,所以在安装salt前需要先安装epel包 # centos5 下载下面rpm  wget -O    epel.rpm https://dl.fedoraproject.org/pu

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

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

HTTP -&gt; Asp.net (第一篇)

当用户在浏览器输入一个URL地址后,浏览器会发送一个请求到服务器.这时候在服务器上第一个负责处理请求的是IIS.然后IIS再根据请求的URL扩展名将请求分发给不同的ISAPI处理. 流程如下: 1.IIS => aspnet_isapi阶段 ISAPI是一个底层的WIN32 API,开发者可以使用这些接口深入到IIS,让IIS支持各种其他处理程序.ISAPI是一个桥接口,通常用于高层次的工具与IIS之间的接驳.例如Windows下的Apache与Tomcat就是构建于ISAPI之上.ISAPI是

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

版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 系列前言 本系列是本人参加微软亚洲研究院,腾讯研究院,迅雷面试时整理的,另外也加入一些其它IT公司如百度,阿里巴巴的笔试面试题目,因此具有很强的针对性.系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”.有了“招式”和“内功心法”,相信你也能对多线程挥洒自如,在笔试面试中顺利的秒杀多线程试题. -------------------------------------华丽的分割线

【线程管理】之篇一

[线程管理]之篇一 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 亲爱我,孝何难:亲恶我,孝方贤. 一.简介 二.简单介绍线程创建和运行 三.线程信息的获取和设置 四.线程中断:interrupt() 或者 使用java异常控制 五.线程的休眠和恢复 六.等待线程的终止 一.简介 并发(Concurrency)指的是一系列任务的同时运行.如果一台电脑多个处理器或者多核处理器,这个同时性是真正意义上的并发:但一电脑只