• 【架构笔记】基础篇03 CPU的运行与其对线程的影响


    概要

    • 什么是线程 CPU是如何理解线程的
    • 为什么英特尔存在4核8线程
    • 程序里头线程是怎么体现的
    • Task,Thread,ThreadPool都有啥关系 Async关键字呢?
    • 对我写程序有啥影响?怎么落地这些知识?

    什么是进程

    进程: 进程这东西非常神奇,如果程序不控制,它可以把CPU跑到断电. 由于有操作系统调度器的关系,进程被弱化到了调度单位. 进程好玩的点在于它是N多资源的集合,例如各种外设.

    实际上,CPU在同一时刻(物理上的,牛爵爷说的),按照标准的描述来说:

    A common form of multitasking is time-sharing. Time-sharing is a method to allow high responsiveness for interactive user applications. In time-sharing systems, context switches are performed rapidly, which makes it seem like multiple processes are being executed simultaneously on the same processor. This seeming execution of multiple processes simultaneously is called concurrency.

    汉语:现代计算机系统可在同一段时间内以进程的形式将多个程序加载到存储器中,并借由时间共享(或称时分复用),以在一个处理器上表现出同时(平行性)运行的感觉。同样的,使用多线程技术(多线程即每一个线程都代表一个进程内的一个独立执行上下文)的操作系统或计算机体系结构,同样程序的平行线程,可在多CPU主机或网络上真正同时运行(在不同的CPU上)。

    什么是线程

    线程(英语:thread)是操作系统能够进行运算调度的最小单位.大部分情况下,它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程. 线程是独立调度和分派的基本单位.线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows的线程,进行混合调度.

    线程有4个状态 产生(spawn) 中断(block) 非中断(unblock) 结束(finish)

    不同的平台,线程的理解也不尽相同. 比如:

    unix有International线程. SUN Solaris操作系统使用的线程叫做UNIX International线程,支持内核线程、轻权进程和用户线程.一个进程可有大量用户线程;大量用户线程复用少量的轻权进程,轻权进程与内核线程一一对应.用户级线程在调用核心服务时(如文件读写),需要“捆绑(bound)”在一个LWP上.永久捆绑(一个LWP固定被一个用户级线程占用,该LWP移到LWP池之外)和临时捆绑(从LWP池中临时分配一个未被占用的LWP).在调用系统服务时,如果所有LWP已被其他用户级线程所占用(捆绑),则该线程阻塞直到有可用的LWP.如果LWP执行系统线程时阻塞(如read()调用),则当前捆绑在LWP上的用户级线程也阻塞. 如果是它的话头文件是Thread.h

    还有一个叫做POSIX的线程 POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准.该标准定义了创建和操纵线程的一整套API.在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程.Windows操作系统也有其移植版pthreads-win32.如果是POSIX的话,头文件是pThread.H 而windows就比较简单了,只有一个Win32线程,包含:寄存器,核心栈,线程环境块与用户栈.

    CPU是如何理解线程的

    线程是CPU的实际调度和分派的基本单位,比如说你写的.NET程序.每一个进程至少得有一个线程.这个是必须.

    而硬件呢?假设我只有一个核. 那也有:叫做超线程技术的多线程. 比如:我计算偶数的命令是一个,计算奇数的命令是另外一个,利用一些命令模式来拆分指令集或者一些其他的方式来区分运作的内容,达到类似多线程的目的,这叫做超线程,是硬件的多线程技术.

    所以:就算是一个核也有多线程,但是并不是完全并行的,因为时间尺度上,它真的做不到了.

    1.单CPU中的进程只能并发,多CPU的才能并行.

    2.单核中线程只能并发,多核才能并行

    为什么英特尔存在4核8线程

    超线程(HT, Hyper-Threading)[1]是英特尔研发的一种技术,于2002年发布。超线程技术原先只应用于Xeon 处理器中,当时称为“Super-Threading”。之后陆续应用在Pentium 4 HT中。早期代号为Jackson。 通过此技术,英特尔实现在一个实体CPU中,提供两个逻辑线程。之后的Pentium D纵使不支持超线程技术,但就集成了两个实体核心,所以仍会见到两个线程。超线程的未来发展,是提升处理器的逻辑线程。英特尔于2016年发布的Core i7-6950X便是将10核心的处理器,加上超线程技术,使之成为20个逻辑线程的产品。 英特尔表示,超线程技术让Pentium 4 HT处理器增加5%的裸晶面积,就可以换来15%~30%的性能提升。但实际上,在某些程序或未对多线程编译的程序而言,超线程反而会降低性能。除此之外,超线程技术亦要操作系统的配合,普通支持多处理器技术的系统亦未必能充分发挥该技术。例如Windows 2000,英特尔并不鼓励用户在此系统中利用超线程。原先不支持多核心的Windows XP Home Edition却支持超线程技术。

    程序里头线程是怎么体现的

    控制CPU

    先增加以下这个类

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Runtime.InteropServices;
      4 using System.Text;
      5 using System.Threading;
      6 
      7 namespace ConsoleApp1
      8 {
      9     public static class ThreadCore
     10     {
     11         [DllImport("kernel32.dll")]
     12         static extern uint GetTickCount();
     13 
     14         //SetThreadAffinityMask 指定hThread 运行在 核心 dwThreadAffinityMask
     15         [DllImport("kernel32.dll")]
     16         static extern UIntPtr SetThreadAffinityMask(IntPtr hThread,
     17         UIntPtr dwThreadAffinityMask);
     18 
     19         //得到当前线程的handler
     20         [DllImport("kernel32.dll")]
     21         static extern IntPtr GetCurrentThread();
     22 
     23         public static void Init()
     24         {
     25             Thread t1 = new Thread(new ParameterizedThreadStart(Sin));
     26             Console.Write("Which core you will to use (Start from 0):");
     27             string core = Console.ReadLine();
     28             int coreNumber = 0;
     29             try
     30             {
     31                 coreNumber = Int32.Parse(core);
     32             }
     33             catch
     34             {
     35                 coreNumber = 0;
     36             }
     37             t1.Start(coreNumber);
     38 
     39 
     40             Console.ReadLine();
     41         }
     42         public static void HalfOfHundred(object coreNumber)
     43         {
     44             int core = 0;
     45             try
     46             {
     47                 core = (int)coreNumber;
     48             }
     49             catch
     50             {
     51                 core = 0;
     52             }
     53             SetThreadAffinityMask(GetCurrentThread(), new UIntPtr(SetCpuID(core)));
     54             int busyTime = 10;//忙碌周期 Windows的调度周期为10MS左右
     55             int idleTime = busyTime;//沉睡周期
     56             Int64 startTime = 0;
     57             while (true)
     58             {
     59                 startTime = System.Environment.TickCount;//当前的时钟周期
     60                 while ((System.Environment.TickCount - startTime) <= busyTime)///计算我应该睡多久
     61                 {
     62                     //这里的空转基本上 就能转到等待的时间直到现在,然后再等对应的时间然后再休息.
     63                     //也就是休息一半转一半
     64                 }
     65                 System.Threading.Thread.Sleep(idleTime);//缓冲一下让CPU休息休息
     66             }
     67 
     68         }
     69 
     70         public static void Sin(object coreNumber)
     71         {
     72             //JAVA C C++ GOLANG RUBY PYTHON .NET LUA VB JS 
     73             int core = 0;
     74             try
     75             {
     76                 core = (int)coreNumber;
     77             }
     78             catch
     79             {
     80                 core = 0;
     81             }
     82             SetThreadAffinityMask(GetCurrentThread(), new UIntPtr(SetCpuID(core)));
     83 
     84             double split = 0.01;
     85             int count = 200;
     86 
     87             double pi = 3.1415962525;
     88 
     89 
     90             int total_amplitude = 300;
     91 
     92             int[] busySpan = new int[count];//来存储每一个周期的忙的时间 
     93 
     94             //计算在每个Y点上的忙碌的CPU碎片数量
     95             int amplitude = total_amplitude / 2;
     96             double radian = 0.0;
     97             for (int i = 0; i < count; i++)
     98             {
     99                 busySpan[i] = (int)(amplitude + amplitude * Math.Sin(pi * radian));
    100                 radian += split;
    101             }
    102 
    103             uint startTime = 0;
    104             int j = 0;
    105             while (true)
    106             {
    107                 j = j % count;
    108                 startTime = GetTickCount();
    109                 while ((GetTickCount() - startTime) <= busySpan[j])
    110                 {
    111 
    112                 }
    113                 Thread.Sleep(total_amplitude - busySpan[j]);
    114                 j++;
    115             }
    116         }
    117 
    118         //函数中的参数 dwThreadAffinityMask 为无符号长整型,用位标识那个核心
    119         //比如:为简洁使用四位表示
    120         //0x0001表示核心1,
    121         //0x0010表示核心2,
    122         //0x0100表示核心3,
    123         //0x1000表示核心4
    124         public static ulong SetCpuID(int id)
    125         {
    126             ulong cpuid = 0;
    127             if (id < 0 || id >= System.Environment.ProcessorCount)
    128             {
    129                 id = 0;
    130             }
    131             cpuid |= 1UL << id;
    132             return cpuid;
    133         }
    134     }
    135 }
    ThreadCore.cs

    然后执行

    ThreadCore.Init();

    即可看见CPU画SIN曲线

    何时控制CPU?

    例子1:日志监控线程 单独控制在某个cpu 某线程运行 保证对业务影响最小化。

    例子2:断点续传选B 重server方案 因为只有服务器知道真实获取到了哪几个包

    两种方案 :

    A:用户 主动推给服务  (重client)

    B:用户 被动等待服务拉取 (重server)

    Task,Thread,ThreadPool都有啥关系

    .NET的线程技术拆分的非常明确. 都存在于System.Thread命名空间下. 不管是Thread,还是ThreadPool,还是TASK都是.

    Thread需要自己调度,适合长跑型的操作。

    ThreadPool是Thread基础上的一个线程池,目的是减少频繁创建线程的开销。

    Task是一个更上层的封装,内部使用Thread or ThreadPool。

    对我写程序有啥影响?

    其实你处处都在考虑线程的问题.

    例如ASP.NET默认帮你处理了多线程,这也就是为什么你可以在ASP.NET中Thread.Sleep()而不导致ASP.NET 网站整体阻塞的原因.

    你写Winfrom你需要赋值的时候会出现什么不能从非UI组建创建的线程去访问它,也是这个原因.所以看起来Winfrom在多线程方面巨安全.

    在单核中要注意抢占式调度的问题,单核中sleep在并发中死循环打死不撒手会导致程序超出意料的问题。

    声明:节选自 ben上课笔记

  • 相关阅读:
    mysql 分页查询及优化
    Mabatis中#{}和${}的区别
    mybatis 缓存(cache)的使用
    mac下安装 rabbitMq
    maven profile动态选择配置文件
    在pom.xml中使用distributionManagement将项目打包上传到nexus私服
    ConfigFileApplicationListener
    【Ubuntu 16】安装nginx
    【Ubuntu 16】安装ssh
    使用XMLHttpRequest异步通信
  • 原文地址:https://www.cnblogs.com/nontracey/p/12887356.html
Copyright © 2020-2023  润新知