Linux 线程介绍:介绍Linux系统中线程的基本概念、创建和调度机制

简介: Linux 线程介绍:介绍Linux系统中线程的基本概念、创建和调度机制


  • 线程的概念

在linux下,线程是最小的执行单位.

线程可看做寄存器和栈的集合.

  • 线程的优点
  1. 开销小,远小于进程
  2. 提高程序并发性,使多CPU系统更加有效.操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上.
  3. 数据通信、共享数据方便,由于同一进程下的线程之间共享数据空间,线程间通信机制比较方便.
  4. 改善程序结构.一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改
  • 线程的共享资源
  1. 内存地址空间 (.text/.data/.bss/heap/共享库)
  2. 全局变量
  3. 信号处理方式
  4. 打开的文件描述符
  5. 用户ID和组ID
  • 线程的非共享资源
  1. 处理器现场和栈指针(内核栈)
  2. 独立的栈空间(用户空间栈)
  3. errno变量
  4. 信号屏蔽字
  • 线程的创建

任何新创建的线程的可消除性状态和类型,包括首次调用main()的线程分别为PTHREAD_CANCEL_ENABLEPTHREAD_CANCEL_DEFERRED。

  • 线程的终止

正常退出方式:

  1. 线程函数返回,返回值就是线程的退出码.
  2. 被同一进程中的其他线程取消,即其他线程调用pthread_cancel(tid,NULL);
  3. 线程内调用pthread_exit(tid,NULL);

异常退出方式:

  • 自行异常终止:      调用abort给自己发一个SIGABRT信号将自己终止.
  • 外界信号异常终止: 进程收到某个信号,通常是别人发的一个信号,将其终止.
  • 线程的分离

线程分离的设置方法

1)在创建线程时,利用thread_create函数的第二个参数设置线程分离;

说明:如果线程已经设置了分离状态,则再调用pthread_join就会失败,可用这个方法验证是否已成功设置分离状态。

2)创建完线程后,通过pthread_detach函数设置线程分离。

一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。

但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。

不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

主线程退出时

当主线程退出时,分离线程(detached threads)会继续运行。然而,整个进程会在主线程退出后随即退出,并且操作系统会回收进程的资源。这意味着,虽然分离线程仍在运行,但它们将无法正常执行。为了避免这种情况,通常需要在主线程退出前等待分离线程完成工作。可以使用类似`pthread_join`的方法实现线程同步。

在这种情况下,分离线程并不会收到特定的信号来终止。当主线程退出导致整个进程结束时,操作系统会清理进程并回收其资源。与发送信号不同,操作系统会直接停止和清理分离线程,而不是向它们发送信号。如果想要清理分离线程,在主线程退出之前,可以使用线程同步机制(如条件变量、信号量或互斥锁等)来确保分离线程完成其工作并正确退出。

  • 线程数据

在单线程的程序里,有两种基本的数据:全局变量和局部变量。

但在多线程程序里,还有第三种数据类型:线程数据(TSD: Thread-Specific Data)。

它和全局变量很象,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的。

这种数据的必要性是显而易见的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;

但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。

我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

  • 线程调度策略

实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,

分时进程则通过nice和counter值决定权值,nice越小,counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。

内核默认调度算法是循环时间分享策略(SCHED_OTHER或SCHED_NORMAL),实时调度策略分为两种SCHED_RR和SCHED_FIFO,

linux系统中,这两个调度策略都有99个优先级,其优先级数值从1(低优先级)~ 99(高优先级),每个优先级又有一个进程队列

  • SCHED_OTHER

它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。

简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。

请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。

这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。

  • SCHED_RR

同优先级的实时线程中,每个进程又是通过获得时间片来分时运行。

不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程。

鉴于SCHED_FIFO调度策略的一些缺点,SCHED_RR对SCHED_FIFO做出了一些增强功能。从实质上看,它还是SCHED_FIFO调用策略。

它使用最大运行时间来限制当前进程的运行,当运行时间大于等于最大运行时间的时候,当前线程将被切换并放置于相同优先级队列的最后。

这样做的好处是其他具有相同级别的线程能在“自私“线程下执行。

SCHED_RR是根据时间片来调度线程的,当时间片用完时,不管这个线程优先级有多高,都不会在运行,而是进入就绪队列,等待下一个时间片到来。

时间片长度的定位是linux凭经验来的,即选择尽可能长、同时能保持良好相应时间的一个时间片。

  • SCHED_FIFO

它是一种实时的先进先出调用策略,且只能在超级用户下运行,同优先级的实时线程,后进入的进程要等前一个进程释放了CPU,才能够运行。

(没有时间片)不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程。

这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。

对于相同优先级别的线程,按照简单的先进先运行的规则运行。

此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。

我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。

  • 线程和信号
  • 线程之间共享信号处理函数
  • 线程有独立的阻塞信号掩码(信号集)
  • 私有挂起信号和共享挂起信号
  • 致命信号下, 进程组全体退出
  • pthread_attr_t结构

__detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自 行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用 pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复 到 PTHREAD_CREATE_JOINABLE状态。

__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。

__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。

__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED, 前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为 PTHREAD_EXPLICIT_SCHED。

__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同 进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。

pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。

为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。

  • 多线程的使用
  • 在CPU比较繁忙,资源不足的时候(开启了很多进程),操作系统只为一个含有多线程的进程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
  • 在CPU资源比较充足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
  • 线程之间的通信方式
  • 全局变量
  • 互斥锁
  • 条件变量,带锁
  • 信号
  • 无名管道
  • 无名信号量
  • 线程函数索引

1.创建线程pthread_create

2.等待线程结束pthread_join

3.分离线程pthread_detach

4.创建线程键pthread_key_create

5.删除线程键pthread_key_delete

6.设置线程数据pthread_setspecific

7.获取线程数据pthread_getspecific

8.获取线程标示符pthread_self

9.比较线程pthread_equal

10.一次执行pthread_once

11.出让执行权sched_yield

12.修改优先级pthread_setschedparam

13.获取优先级pthread_getschedparam

14.发送信号pthread_kill

15.设置线程掩码pthread_sigmask

16.终止线程pthread_exit

17.退出线程pthread_cancel

18.允许/禁止退出线程pthread_setcancelstate

19.设置退出类型pthread_setcanceltype

20.创建退出点pthread_testcancel

21.压入善后处理函数pthread_cleanup_push

22.弹出善后处理函数pthread_cleanup_pop

目录
打赏
0
0
0
0
216
分享
相关文章
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
65 27
|
7天前
|
Linux系统ext4磁盘扩容实践指南
这个过程就像是给你的房子建一个新的储物间。你需要先找到空地(创建新的分区),然后建造储物间(格式化为ext4文件系统),最后将储物间添加到你的房子中(将新的分区添加到文件系统中)。完成这些步骤后,你就有了一个更大的储物空间。
52 10
深度体验阿里云系统控制台:SysOM 让 Linux 服务器监控变得如此简单
作为一名经历过无数个凌晨三点被服务器报警电话惊醒的运维工程师,我对监控工具有着近乎苛刻的要求。记得去年那次大型活动,我们的主站流量暴增,服务器内存莫名其妙地飙升到90%以上,却找不到原因。如果当时有一款像阿里云 SysOM 这样直观的监控工具,也许我就不用熬通宵排查问题了。今天,我想分享一下我使用 SysOM 的亲身体验,特别是它那令人印象深刻的内存诊断功能。
|
9天前
|
微服务2——MongoDB单机部署4——Linux系统中的安装启动和连接
本节主要介绍了在Linux系统中安装、启动和连接MongoDB的详细步骤。首先从官网下载MongoDB压缩包并解压至指定目录,接着创建数据和日志存储目录,并配置`mongod.conf`文件以设定日志路径、数据存储路径及绑定IP等参数。之后通过配置文件启动MongoDB服务,并使用`mongo`命令或Compass工具进行连接测试。此外,还提供了防火墙配置建议以及服务停止的两种方法:快速关闭(直接杀死进程)和标准关闭(通过客户端命令安全关闭)。最后补充了数据损坏时的修复操作,确保数据库的稳定运行。
40 0
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
57 17
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
63 26
|
3月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
319 2
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3月前
|
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
80 10

热门文章

最新文章