热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

查linux有哪些task_浅谈Linux线程模型

ThreadBasic基础概念线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。从定义中可以看出,线程它是操作系统的概念&#x

e02cd60027551b01e48fea0a4140cf2b.png

Thread Basic

基础概念

线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。从定义中可以看出,线程它是操作系统的概念,在不同的操作系统中的实现是不同的,不过今天分享的猪脚是Linux Thread。

对于Linux操作系统而言,它对Thread的实现方式比较特殊。在Linux内核中,其实是没有线程的概念的,它把所有的线程当做标准的进程来实现,也就是说Linux内核,并没有为线程提供任何特殊的调度语义,也没有为线程实现特定的数据结构。取而代之的是,线程只是一个与其他进程共享某些资源的进程。每一个线程拥有一个唯一的task_struct结构,Linux内核它仅仅把线程当做一个正常的进程,或者说是轻量级进程,LWP(Lightweight processes)。

对于其他的操作系统而言,比如windows,线程相对于进程,只是一个提供了更加轻量、快速执行单元的抽象概念。对于Linux而言,线程只是进程间共享资源的一种方式,非常轻量。举个简单例子,假设有一个进程包含了N个线程。对于那些显示支持线程的操作系统而言,应该是存在一个进程描述符,依次轮流指向N个线程。这个进程描述符指明共享资源,包括内存空间和打开的文件,然后线程描述它们自己独享的资源。相反的是在Linux中,只有N个进程,因此有N个task_struct数据结构,只是这些数据结构的某些资源项是共享的。

这里再总结一下,Linux线程是进程资源共享的一种方式,而其他操作系统,线程则是一种实现轻量、快速执行单元的抽象概念或者实体。这里再深入的理解一下,Linux中的线程和进程的区别。这也是诸多面试题中,最常见的一个。

资源共享

Linux线程与进程的区别,主要体现在资源共享、调度、性能几个方面,首先看一下资源共享方面。上面也提到,线程其实是共享了某一个进程的资源,这些资源包括:

  • 内存地址空间
  • 进程基础信息
  • 大部分数据
  • 打开的文件
  • 信号处理
  • 当前工作目录
  • 用户和用户组属性
  • 等等

哪些是线程独自拥有的呢?

  • 线程ID
  • 一系列的寄存器
  • 栈的局部变量和返回地址
  • 错误码 errno
  • 信号掩码
  • 优先级
  • 等等

这里说一个黑科技,线程拥有独立的调用栈,除了栈之外共享了其他所有的段segment。但是由于线程间共享了内存,也就是说一个线程,理论上是可以访问到其他线程的调用栈的,可以用一个指针变量,去访问其他线程的局部栈帧,以访问其他线程的局部变量。

调度

说到调度,就得提到进程的上下文切换。上下文切换也被称作为进程调度或者任务切换,简单的来说是把CPU从一个进程或者线程切换到另一个执行。概括的来说,线程的上下文切换,要比进程更加快速,因为本质上,线程很多资源都是共享进程的,所以切换时,需要保存和切换的项是很少的。

线程上线文切换时,虚拟地址空间是不变的,但是进程上下文切换时,是需要重新映射虚拟地址空间。进程切换上下文时,进出OS内核&寄存器切换,是最大的时间支出。更模糊的代价是上下文切换时,会干扰处理器的缓存机制。当上下文切换时,处理器需要重新cache一些内存。

这里更大的一个区别时,当更改虚拟地址空间时,CPU 的 TLB 等也会被刷新,导致接下来的内存访问更加耗时,所以相对线程切换来说,进程的切换耗时更大。

性能

从性能方面,来查看一下线程与进程的对比。由于线程更加轻量,导致线程的创建速度、切换速度都要高于进程。这里就有一个疑问了,从上面提到的各个方面来看,好像线程都要优于进程,那么有没有啥缺点呢?

线程缺点

线程同样也有缺点,最大的缺点是线程的不安全性,缺乏保护机制。就是上面提到的黑科技,因为线程间共享数据,一个线程可以重写另外一个线程的堆栈,导致出现一些异常的情况。除此之外,线程还有以下缺点:

  • 共享属性:全局变量是在所有线程间共享的,访问时是需要同步加锁。
  • 很多库函数是线程非安全的,多线程编程时,需要注意这一点。
  • 线程的健壮性不强,如果一个线程crash了,那么整个应用程序就跪了。

应用场景

上面提到了线程与进程的对比,也提到了线程的优点和缺点,那么什么情况下适合用线程呢?简单的来说,计算密集型的任务,适合于多线程来处理。因为计算密集型任务,需要耗费很多CPU,上下文的切换是非常频繁的,而线程切换速度是高于进程的,所以使用线程是更加适合的。在实际的编程过程中,根据业务的场景,再结合进程和线程的优缺点对比,来决定适合的编程模型。

线程创建

那么Linux中线程是如何创建出来的呢?上面也提到,在Linux中线程是一种资源共享的方式,可以在创建进程的时候,指定某些资源是从其他进程共享的,从而在概念上创建了一个线程。在Linux中,可以通过clone系统调用来创建一个进程,它的函数签名如下:

#include
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);

我们在使用clone创建进程的过程中,可以指明相应的参数,来决定共享某些资源,比如:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

这个clone系统调用的行为类似于fork,不过新创建出来的进程,它的内存地址、文件系统资源、打开的文件描述符和信号处理器,都是共享父进程的。换句话说,这个新创建出来的进程,也被叫做Linux Thread。从这个例子中,也可以看出Linux中,线程其实是进程实现资源共享的一种方式。

内核线程

在Linux中,还存在一个Kernel Thread的概念,也就是内核线程。内核创建一些内核线程来执行一些后台任务。相对于普通的进程,内核线程完整的存在于内核空间,是没有自己的地址空间的,也就是mm指针为空,它的操作仅存在于内核态,并且也不会上下文切换到用户态。不过内核线程和普通进程类似的是,是可调度和可抢占的。

同步

由于线程间共享了很多资源,所以在多线程的编程环境下,为了保障结果的准确性和一致性,需要对共享资源的访问进行同步。常见的同步方式,也就是加锁,以保障操作共享资源时,不会出错。在Linux中,锁的种类大致有四种:

  • 互斥锁
  • 读写锁
  • 条件变量
  • 自旋锁
  • 内存屏障

有兴趣的同学,看看看下这篇文章:http://blog.lecury.cn/2016/02/21/%E5%90%8C%E6%AD%A5%E4%BA%92%E6%96%A5(%E9%94%81).html 。总结来说,锁的代价是高昂的,所以在设计高并发、高吞吐的程序时,尽量避免锁的使用,或者减少锁的区间。

常见的多线程编程模式

下面谈一下实际工作中,要如何合理的线程呢?这里我简单的提出三种常见的线程模型。

  • leader-follow 模型(主从)
    • 线程与连接对应,并发度等于线程数。
    • 所有线程经历accept->close整个过程。
    • 适用于连接数少、处理时间长、CPU密集型服务。
  • producer-consumer模型(生产者消费者)
    • 主线程用于accept请求,并将fd放置在消费队列pendingpool中。
    • pendingpool进行连接的维护工作。
    • 多个worker竞争pendingpool的连接。
    • 适用于连接数多、处理速度快的业务。
  • 高并发索引模型
    • 无锁设计
    • 将请求或者事务映射到具体线程处理

踩过的坑和小技巧

  • 同步
  • 过载保护
  • 公平调度
  • 析构出core

更加详细的内容,请参考知乎Live:

如何理解和应用Linux线程模型?​www.zhihu.com
fe2069a89c97863fb81629ef7438727f.png

感兴趣的同学,可以加QQ群: 853832829 一起学习~



推荐阅读
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • 用阿里云的免费 SSL 证书让网站从 HTTP 换成 HTTPS
    HTTP协议是不加密传输数据的,也就是用户跟你的网站之间传递数据有可能在途中被截获,破解传递的真实内容,所以使用不加密的HTTP的网站是不 ... [详细]
  • 本文详细介绍了如何解决DNS服务器配置转发无法解析的问题,包括编辑主配置文件和重启域名服务的具体步骤。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 解决 Windows Server 2016 网络连接问题
    本文详细介绍了如何解决 Windows Server 2016 在使用无线网络 (WLAN) 和有线网络 (以太网) 时遇到的连接问题。包括添加必要的功能和安装正确的驱动程序。 ... [详细]
  • 在Windows系统中安装TensorFlow GPU版的详细指南与常见问题解决
    在Windows系统中安装TensorFlow GPU版是许多深度学习初学者面临的挑战。本文详细介绍了安装过程中的每一个步骤,并针对常见的问题提供了有效的解决方案。通过本文的指导,读者可以顺利地完成安装并避免常见的陷阱。 ... [详细]
  • 性能测试中的关键监控指标与深入分析
    在软件性能测试中,关键监控指标的选取至关重要。主要目的包括:1. 评估系统的当前性能,确保其符合预期的性能标准;2. 发现软件性能瓶颈,定位潜在问题;3. 优化系统性能,提高用户体验。通过综合分析这些指标,可以全面了解系统的运行状态,为后续的性能改进提供科学依据。 ... [详细]
author-avatar
3051451abcd
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有