前言

这俩个月没怎么写文章做记录分享,一直在忙项目上线的事情,但是学习这件事情,停下来就感觉难受,clr线程这章也是反复看了好多遍,书读百遍其义自见,今天我们来聊下线程基础

1.进程是什么,以及线程起源

2.线程开销,以及上线文切换

3.使用线程的理由

4.线程调度和优先级

5.前台线程和后台线程

一、进程是什么,以及线程起源

在计算机的早期岁月,os没有线程的概念,整个系统只运行者一个执行线程,其中包含操作系统和应用程序的代码。这意味着长时间运行的任务会阻止其他任务的运行。在16位windows的那些日子,打印文档的应用程序很容易“冻结”整个机器,造成os和其他应用程序停止响应。遇到这个问题,用户只能按Reset建或重启计算机,所有正在运行的应用程序都会终止,造成应用程序正在处理的数据都会无端的丢失,用户对此深恶痛绝。

Microsoft由此设计出新的OS内核,决定在一个进程中运行应用程序的每个实例,进程实际是 应用程序的实例要使用的资源的集合。每个进程都赋予了一个虚拟的地址空间,确保在一个进程中使用的代码和数据无法由另一个进程访问,进程也访问不了OS的内核代码和数据,所以,应用程序破坏不了其他应用程序或者OS自身,用户体验变得更好了。

听起来不错,但CPU本身呢?应用程序发生死循环会发生什么?如果机器只有一个CPU,它会执行死循环,虽然数据无法被破坏,但系统仍然可能停止响应。Microsoft拿出的解决方案就是线程。作为一个Windows概念,线程的职责是对CPU进行虚拟化。Windows为每个进程都提供了该进程专用的线程(功能相当于一个CPU)。应用程序的代码进入死循环,与那个代码关联的进程会“冻结”,但其他进程(他们有自己的线程)不会冻结,他们会继续执行!

二、线程开销

1.线程内核对象(thread kernel object)

OS为系统中创建的每个线程都分配并初始化这种数据结构之一。数据结构包含一组对线程进行描述的属性。线程结构还包含所谓的线程上下文(thread context)。上下文是包含CPU寄存器集合的内存块。对于x86,x64和ARM CPU架构,线程上下文分别使用约700,1240和350字节的内存

2.线程环境快(thread environment block,TEB)

TEB是在用户模式(应用程序代码能快速访问的地址空间)中分配和初始化的内存块。TEB耗用一个内存页(x86,x64,和ARM CPU中是4KB)。TEB包含线程的异常处理链首(head)。线程进入的每个try块都在链首插入一个节点(node);线程退出try块时从链中删除该节点。此外,TEB还包含线程的“线程本地存储”数据,以及GDI(Graphics Device Interface,图形设备接口)和OpenGL图形使用的一些数据结构

3.用户模式栈(user-mode stack)

用户模式栈存储传给方法的局部变量和实参。它还包含一个地址;指出当前方法返回时,线程应该从什么地方接着执行。Windows默认为每个线程的用户模式栈分配1MB内存。

4.内核模式栈(kernel-mode-stack)

应用程序代码向操作系统中的内核模式函数传递实参时,还会使用内核模式栈。由于对安全的考虑,针对从用户模式的代码传给内核的任何实参,Windows都会把它们从线程的用户模式栈复制到线程的内核模式栈。一经复制,内核就可验证实参的值。由于应用程序代码不能访问内核模式栈,所以应用程序无法更改验证后的实参值。OS内核代码开始处理复制的值。除此之外,内核会调用它自己内部的方法,并利用内核模式栈传递它自己的实参、存储函数的局部变量以及存储返回地址。

4.DLL线程连接(attach)和线程分离(detach)通知

Windows的一个策略是,任何时候在进程中创建线程,都会调用进程中加载的所有非托管DLL的DllMain方法,并向该方法传递DLL_THREAD_ATTACH标志。类似地,任何时候线程终止,都会调用进程中的所有非托管DLL的DllMain方法,并向方法传递DLL_THREAD_DETACH标志。有的DLL需要获取这些通知,才能为进程中创建/销毁的每个线程执行特殊的初始化或(资源)清理操作。

也就是说线程创建销毁时会调用所有DLL中的这个函数,严重影响了进程中创建和销毁线程的性能

上下文切换

单CPU计算机一次只能做一件事情。Windows必须在系统中的所有线程(逻辑CPU)之间共享物理CPU。

Windows任何时刻只将一个线程分配给一个CPU 。那个线程能运行一个“时间片”(有时也成为“量”或者“量程”,即quantum)的长度。时间片到期,Windows就上下文切换到另一个线程。每次上下文切换都要求windows执行以下操作。

1.将CPU寄存器的值保存到当前正在运行的线程的内核对象内部的一个上下文结构中。

2.从现有线程集合中选出一个线程供调度。

3.将所选上下文结构中的值加载到CPU的寄存器中。

上下文切换完成后,CPU执行所选的线程,直到它的时间片到期。然后发生下次上下文切换。Windows大约每30毫秒执行一次上下文切换。

要构建高性能应用程序和组件,就应该尽量避免上下文切换。

减少线程的数量也会显著提升垃圾回收的性能。

三、使用线程的理由

1.可响应性(通常是对于客户端GUI应用程序)

例如windows中每个进程提供它自己的线程,确保发生死循环的应用程序不会妨碍其它应用程序。类似地,在客户端GUI应用程序中,可以将一些工作交给一些线程进行,使GUI线程能灵敏的响应用户输入。

2.性能(对于客户端和服务端应用程序)

由于windows每个CPU调度一个线程,而且多个CPU能并发执行这些线程,所以同时执行多个操作能提升性能。

满足以下任何条件,就可考虑显式创建自己的线程

1.线程需要以非普通线程优先级运行

2.需要线程表现为一个前台线程,防止应用程序在线程结束任务前终止

3.计算限制的任务需要长时间运行

4.要启动的线程,并可能调用Thread的abort方法来提前终止它

四、线程调度和优先级

每个线程都分配了从0(最低)到31(最高)的优先级。

只要存在可调度的优先级31的线程,系统就永远不会将优先级0~30的任何线程分配给CPU。这种情况成为饥饿。

系统启动时会创建一个特殊的零页线程(zero page thread)。该线程的优先级为0,而且是整个系统唯一优先级为0的线程。在没有其他线程需要“干活儿”的时候,零页线程将系统RAM的所有空闲页清零。

五、前台线程和后台线程

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading; namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
//创建新线程(默认为前台线程)
Thread t = new Thread(Worker);
//使线程成为后台线程
t.IsBackground = true;
//启动线程
t.Start();
//如果t是前台线程,则应用程序大概10秒后才终止
//如果t是后台线程,则应用程序立即终止
Console.WriteLine("Returing from Main");
} private static void Worker()
{
//模拟做10秒钟的工作
Thread.Sleep();
//下面这行代码只有由一个前台线程执行时才会显示
Console.WriteLine("Returning from Worker");
}
}
}

应用程序的主线程以及通过构造一个Thread对象来显式创建的任何线程都默认为前台线程。相反,线程池线程默认为后台线程。

另外,由进入托管执行环境的本机(native)代码创建的任何线程都被标记为后台线程。

天道酬勤,大道至简,坚持。

CLR via C# 读书笔记-26.线程基础的更多相关文章

  1. 《CLR via C#》读书笔记 之 线程基础

    第二十五章 线程基础 2014-06-28 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的 ...

  2. [Clr via C#读书笔记]Cp4类型基础

    Cp4类型基础 Object类型 Object是所有类型的基类,有Equals,GetHashCode,ToString,GetType四个公共方法,其中GetHashCode,ToString可以o ...

  3. 《Windows核心编程》读书笔记.Chapter06线程基础

    原文链接在印象笔记(效果也好的多):https://app.yinxiang.com/l/AAQlNLnxTPRMAppVr5W0upchipQDDC_FHlU 概要: 现成也有两个组成部分: 现成的 ...

  4. CLR Via CSharp读书笔记(26) - 计算限制的异步操作

    执行上下文: 执行上下文包括安全设置(压缩栈.Thread的Principal属性和Windows身份),宿主设置(System.Threading.HostExecutionContextManag ...

  5. 《CLR Via C#》读书笔记:26.线程基础

    一.线程开销 操作系统创建线程是有代价的,其主要开销在下面列举出来了. 内存开销 线程内核对象 拥有线程描述属性与线程上下文,线程上下文占用的内存空间为 x86 架构 占用 700 字节.x64 架构 ...

  6. (CLR via C#学习笔记)异步操作 - 线程池

    一 线程池基础 1.线程池维护了一个操作请求队列,将请求的操作追加到线程池队列中,线程池的代码从队列中提取操作项,派发给线程池中的线程; 2.CLR初始化时,线程池中是没有线程的,当有操作派发给线程池 ...

  7. CLR via C# 读书笔记-21.托管堆和垃圾回收

    前言 近段时间工作需要用到了这块知识,遂加急补了一下基础,CLR中这一章节反复看了好多遍,得知一二,便记录下来,给自己做一个学习记录,也希望不对地方能够得到补充指点. 1,.托管代码和非托管代码的区别 ...

  8. Java并发读书笔记:线程安全与互斥同步

    目录 导致线程不安全的原因 什么是线程安全 不可变 绝对线程安全 相对线程安全 线程兼容 线程对立 互斥同步实现线程安全 synchronized内置锁 锁即对象 是否要释放锁 实现原理 啥是重进入? ...

  9. Clr Via C#读书笔记---I/O限制的异步操作

    widows如何执行I/O操作      构造调用一个FileStream对象打开一个磁盘文件-----FileStream.Read方法从文件中读取数据(此时线程从托管代码转为本地/用户模式代码)- ...

随机推荐

  1. 20164317《网络对抗技术》Exp4 恶意代码分析

    1.实践目标 1.是监控你自己系统的运行状态,看有没有可疑的程序在运行. 2.是分析一个恶意软件,就分析Exp2或Exp3中生成后门软件:分析工具尽量使用原生指令或sysinternals,systr ...

  2. Ubuntu16.04 - 安装RabbitVCS,linux下的TortoiseSVN!!!

    RabbitVCS 官网:http://rabbitvcs.org/ 1,添加PPA源.在shell里面执行下面命令: sudo add-apt-repository ppa:rabbitvcs/pp ...

  3. python 通过 pymysql模块 操作 mysql 数据库

    Python 中操作 MySQL 步骤 安装模块 pip install pymysql 引入模块 在py文件中引入pymysql模块 from pymysql import * Connection ...

  4. sql语句_统计总成绩最高的前2名

    有一个数据表,id user_id score 三个字段,计算总成绩最高的前两名 SELECT * FROM (SELECT user_name,SUM(score) AS score FROM us ...

  5. 六,apache修改默认根文件路径

    1,安装完apache后,项目默认根路径是E:\Program Files\AppServ\www. 默认根路径在httpd.conf中默认配置过了,我们可以通过修改配置文件改变项目默认根路径. Do ...

  6. mybatis中单个参数的引用

    单个参数时在test条件中不能用参数名来引用,可以使用_parameter

  7. Linux 操作日志

    这里: /var/log/messages

  8. elasticsearch Geo Distance Query

    Geo Distance Query 过滤器文档只包括在一个特定距离内存在于一个地理点上的命中.假设下列映射和索引文档: PUT /my_locations { "mappings" ...

  9. 如何利用反射简化Servlet操作

    如何利用反射简化Servlet操作   一.反射的实现 新建类BaseServlet,继承HttpServlet(不需要在web.xml文件中配置) 1.在doPost()方法中处理请求乱码,并调用d ...

  10. js之global 对象 方法

    global 作为js的全局对象,但其是无法直接访问的,但是在浏览器中浏览器是将这个对象当做是window对象的一部分,即Date 等Global的属性使用window.Date 可访问到 1.url ...