线程详解

以下内容摘自《程序员的自我修养》

什么是线程?

线程(Thread),有时被称为轻量级(Lightweight Process, LWP),是程序执行流程的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成 。通常意义上,一个进程由一个到多个线程,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程级的资源(如打开文件和信号)。一个经典的线程与进程的关系如图:

大多数软件应用中,线程的数量都不止一个。多个线程可以互不干扰地并发进行,并共享进程的全局变量和堆的数据。那么,多个线程与单线程的进程相比,又有哪些优势呢?通常来说,使用多线程的原因有如下几点:

  • 某个操作可能会陷入长时间等待,等待的线程会进入睡眠状态,无法继续执行。多线程执行可以有效利用等待的时间。
  • 某个操作(常常是计算)会消耗大量的时间,如果只有一个线程,程序和用户之间的交互会中断。多线程可以让一个线程负责交互,另一个线程负责计算。
  • 程序逻辑本身要求并发操作,例如一个多端下载软件
  • 多CPU或多核计算机,本身具备同时执行多个线程的能力,因此单线程程序无法全面发挥计算机的全部计算能力。
  • 相对于多进程应用,多线程在数据共享方面的效率高得多。

线程访问权限

线程的访问非常自由,它可以访问进程内存里的所有数据,甚至包括其他线程的堆栈(如果它知道其他线程的堆栈地址,那么就是很少见的情况),但实际运用中线程也拥有自己的私有存储空间,包括以下几方面:

  • 线程局部存储
  • 寄存器

线程调度与优先级

不论是在多处理器的计算机上还是单处理器的计算机上,线程总是“并发”执行的。当线程的数量小于等于处理器的数量时(并且操作系统支持多处理器),线程的并发是真正的并发,不同的线程运行在不同的处理器上,彼此之间互不相干。但对于线程数量大于处理器的情况,线程的并发会受到一些阻碍,因此此时至少一个处理器会运行多线程。 在单处理器对应多线程的情况下,并发是一种模拟出来的状态。操作系统会让这些多线程程序轮流执行,每次仅执行一小段时间,(通常是几十到几百毫秒),这样每一个线程就“看起来”在同时执行。这样的一个不断在处理器上切换不同的线程的行为称之为线程调度。在线程调度中,线程通常拥有三种状态,分别是:

  • 运行(Running)
  • 就绪(Ready)
  • 等待(Waiting) 处于运行中的线程拥有一段可执行的时间,这段时间称为时间片。当时间片用尽后的时候,进程就开始等待某事件,那么它将进入等待状态。每当一个线程离开运行状态时,调度系统就会选择一个其他的就绪线程继续执行。在一个处于等待状态的线程锁等待的事件发生之后,该线程就将进入就绪状态;

线程调度自多任务操作系统问世以来,就不断被提出不同的方案和算法,现在主流的调度方式尽管各不相同,但都带有优先级调度和轮转法。 在windows中可以通过

BOOL WINAPI SetThreadPriority(HANDLE hThread, int nPriority);

来设置线程的优先级,而linux下与线程相关的操作可以通过pthread库来实现;

让我们总结一下,在优先级调度的环境下,线程的优先级改变一般有三种方式。

  • 用户指定优先级
  • 根据进入等待状态的频繁程序提升或降低优先级
  • 长时间得不到执行而被提升优先级

可抢占线程和不可抢占线程

我们之前讨论的线程调度有一个特点,那就是线程在用尽时间片之后会被强制剥夺继续执行的权利,而进入就绪状态,这个过程叫做抢占。 在不可抢占的线程中,线程主动放弃执行无非是两种情况。

  • 当线程试图等待某一事件时(I/O等)。
  • 线程主动放弃时间片 因此,在不可抢占线程执行时候,有一个显著的特点,那就是线程调度的时间是确定的。线程调度只会发生在线程主动放弃执行或线程等待某事件的时候。这样可以避免一些因为抢占式线程里调度时间不确定而产生的问题。

Linux的多线程

Windows对进程和线程的实现如同教科书一般标准,windows内核有明确的线程和进程的概念,并且有一系列的API来操纵它们。但对于linux来说,线程并不是一个通用的概念。 Linux对多线程的支持颇为贫乏,事实上,在Linux内核中并不存在真正意义上的线程概念。Linux将所有的执行实体(无论是线程还是进程)都称为任务(Task),每一个任务概念上不同任务之间都可以选择共享空间,因此在实际意义上,共享同一个内存空间的多个任务构成了一个进程,这些任务也就成了这个进程中的线程。在Linux下,用以下方法可以创建一个新的任务:

fork函数产生一个和当前进程完全意义的新进程,并和当前进程一样从fork函数里返回。例如如下代码:

pid_t pid;
if (pid = fork())
{
    ....
}

fork只能够产生本任务的镜像,因此须要使用exec配合才能启动别的新任务。exec可以用新的可执行映像替换当前的可执行镜像

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • main函数是主线程吗

    1、线程的概念: 线程是程序最基本的运行单位,而进程不能运行,所以能运行的,是进程中的线程。 2、线程是如何创建起来的: 进程仅仅是一个容器,包含了线程运行中所...

    233333
  • Linux下的进程类别(内核线程、轻量级进程和用户进程)--Linux进程的管理与调度(四)

    虽然我们在区分Linux进程类别, 但是我还是想说Linux下只有一种类型的进程,那就是task_struct,当然我也想说linux其实也没有线程的概念, 只...

    233333
  • Linux内核线程kernel thread详解--Linux进程的管理与调度(十)

    Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。

    233333
  • 《编写高质量代码》学习笔记(3)

    建议125:优先选择线程池 在Java1.5之前,实现多线程比较麻烦,需要自己启动线程,并关注同步资源,防止出现线程死锁等问题,在1.5版本之后引入了并行计算框...

    我没有三颗心脏
  • 线程池之ThreadPoolExecutor概述

    Java源码里面都有大量的注释,认真读懂这些注释,就可以把握其七分工作机制了。关于ThreadPoolExecutor的解析,我们就从其类注释开始。

    用户6182664
  • Java线程池详解

    线程能够充分合理地协调利用CPU、内存、I/O等系统资源,但是线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有空间,在线程销毁时需要回收这些系统资源...

    公众号 IT老哥
  • 全网最火Java面试题

    1.JAVA基础扎实、熟练运用设计模式、理解IO/NIO、反射、多线程编程、了解JVM原理;

    公众号 IT老哥
  • 线程池ThreadPoolExecutor源码分析

    多线程是我们日常工作中很少能接触到的技术,但是面试的时候100%会被问到,万一工作中用到了基本不会,本篇咱们就来深入分析线程池的实现类ThreadPoolExe...

    公众号 IT老哥
  • 【Java多线程-7】阅尽Java千般锁

    根据对同步资源处理策略不同,锁在宏观上分为乐观锁与悲观锁,这只是概念上的一种称呼,Java中并没有具体的实现类叫做乐观锁或者悲观锁。

    云深i不知处
  • 【Java多线程-2】Java线程池详解

    通过前文 线程的创建与使用 ,我们对线程有了一定了解。线程的创建与销毁需要依赖操作系统,其代价是比较高昂的,频繁地创建与销毁线程对系统性能影响较大。

    云深i不知处

扫码关注云+社区

领取腾讯云代金券