前言

这俩个月没怎么写文章做记录分享,一直在忙项目上线的事情,但是学习这件事情,停下来就感觉难受,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. 在Windows安装Reids 详解

    今天安装了redis,记录点经验 因为Redis项目没有正式支持Windows. 但Microsoft开发和维护一个针对Windows 64版的redis. 下载地址在微软的GitHub上,地址:ht ...

  2. DateTimeField如何自动设置为当前时间并且能被修改 ——django日期时间字段的使用

    参考于:https://www.cnblogs.com/huchong/p/7895263.html 创建django的model时,有DateTimeField.DateField和TimeFiel ...

  3. 基于Zookeeper实现的分布式互斥锁 - InterProcessMutex

    Curator是ZooKeeper的一个客户端框架,其中封装了分布式互斥锁的实现,最为常用的是InterProcessMutex,本文将对其进行代码剖析 简介 InterProcessMutex基于Z ...

  4. vsftp小记

    安装一个vsftp都有问题(Version: 3.0.2-14ubuntu1),提示530 错误,之后修改配置如下(红色): # cat /etc/pam.d/vsftpdauth required ...

  5. Linux命令学习与使用2

    1.Ctrl+a:跳到命令行首 Ctrl+E: 跳到命令行尾 Ctrl+L:清屏2.切换用户 su - 用户名3.更换yum镜像源 1.进入/etc/yum.repos.d 备份CentOS-Base ...

  6. DOM操作复选框

    DOM操作复选框 实现一个效果如下图: 点击"全选/全不选"复选框之后,足球.篮球.游泳和唱歌均被选取.取消"全选/全不选"复选框之后,被选中的所有都变成未选中 ...

  7. Unity色子的投掷与点数的获得(详解)

    前几天需要一个色子的投掷并且获得朝上点数的Unity脚本,在网上找了很多,都是一个模子刻出来的. 对于2018版的我来说,网上找的都是很早就弃用了的老版本. 好不容易能运行了,结果并不理想,于是又突发 ...

  8. MATLAB基础操作笔记

    A( i , : ) 表示 A矩阵的第 i 行所有元素 A( : , j ) 表示 A矩阵的第 j 列所有元素 A( : , : ) 表示 A矩阵的 所有元素 定义函数时,函数中有嵌套函数需要在结束时 ...

  9. thuwc2019总结

    275,是我的自己的估分 而350,是面试线 就发挥而言,这次的发挥相当糟糕,第一天选择全场打暴力而不打签到题正解,第二天因A题思路想偏造成2h额外时间花费.第二题与第三题之间,我选择了难打的第三题而 ...

  10. js集合set类的实现

    js集合set类的实现 /*js集合set类的实现*/ function Set() { this.dataStore = []; this.add = add;//新增元素 this.remove ...