在linux下,线程是最小的执行单位.
线程可看做寄存器和栈的集合.
- 开销小,远小于进程
- 提高程序并发性,使多CPU系统更加有效.操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上.
- 数据通信、共享数据方便,由于同一进程下的线程之间共享数据空间,线程间通信机制比较方便.
- 改善程序结构.一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改
- 内存地址空间 (.text/.data/.bss/heap/共享库)
- 全局变量
- 信号处理方式
- 打开的文件描述符
- 用户ID和组ID
- 处理器现场和栈指针(内核栈)
- 独立的栈空间(用户空间栈)
- errno变量
- 信号屏蔽字
任何新创建的线程的可消除性状态和类型,包括首次调用main()的线程分别为PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DEFERRED。
正常退出方式:
- 从线程函数中返回,返回值就是线程的退出码.
- 被同一进程中的其他线程取消,即其他线程调用pthread_cancel(tid,NULL);
- 线程内调用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的时候要小心处理相同级别线程的动作。
- 线程之间共享信号处理函数
- 线程有独立的阻塞信号掩码(信号集)
- 私有挂起信号和共享挂起信号
- 致命信号下, 进程组全体退出
__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