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

目录
相关文章
|
18天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
151 78
|
22天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
54 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
18天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
84 13
|
19天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
40 0
|
13天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
38 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
63 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
41 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
28 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
45 2
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
52 1