Linux线程更多细节

以下内容引述至《Linux/Unix系统编程手册》

线程栈

创建线程时,每个线程都有一个属于自己的线程栈,且大小固定,除主线程外的所有线程,其栈的缺省大小均为2MB,也可以调用pthread_attr_setstack()设置线程属性决定线程站的大小。

线程和信号

Unix信号模型是基于Unix进程模型而设计的,问世比Pthread要早几十年。所以信号与线程模型之间存在一些明显的冲突。主要是因为一方面,针对单线程进程要保持传统的信号语义,于此同时,又需要开发出使用与多线程进程环境的新信号模型。

Unix信号模型与线程映射

要了解Unix信号如何映射到Pthreads模型,就需要了解,信号模型的那些方面属于进程层面(由进程中的所有线程所共享),那些方面是属于进程中的单个线程层面。

  • 信号动作属于进程层面。如果某进程的任一线程收到任何未经处理的信号,且其缺省动作为stop或terminate,那么将停止或者终止该进程的所有线程
  • 对信号的处置属于进程层面,进程中的所有线程共享对每个信号的处置设置。如果某一线程使用函数sigaction()为某类信号创建了处理函数,那么当收到SIGINT时,任何线程都会去调用该处理函数;同样,如果对信号设置为忽略,那么所有的线程都会忽略该信号
  • 信号的发生既可针对整个进程,也可针对某个特定线程
  • 当多线程程序收到一个信号,且该进程已然为此信号创建了信号处理程序时,内核会任选一条线程来接收这一信号,并在该线程中调用信号处理程序对其进行处理
  • 信号掩码是针对每个线程而言的,使用Pthread API所定义的函数pthread_sigmask(),各线程可独立阻止或放行各种信号。
  • 针对为整个进程所挂起的信号,以及为每条线程所挂起的信号,内核都分别维护有记录
  • 如果信号处理程序中断了对pthread_mutex_lock的调用,那么该调用总是会自动重新开始
  • 备选信号栈是每个线程特有的

操作线程信号掩码

刚创建的新线程从其创建者处集成信号掩码的一份拷贝,线程使用pthread_sigmask来改变并获取当前的信号掩码

#include <pthread.h>
int pthread_sigmask(int how, const sigset_t* set, sigset_t *oldset);

除了所操作的线程信号掩码之外,pthread_sigmask与sigprocmask的用法完全相同。

向线程发送信号

函数pthread_kill()向同一进程下的另一线程发送信号sig

#include <pthread.h>
int pthread_kill(pthread_t thread, int sig);

#define _GNU_SOURCE
#include <signal.h>
int pthread_sigqueue(pthread_t thread, int sig, const union sigval value);

使用pthread_sigqueue向同一进程中的另一线程发送携带数据的信号

异步信号处理

没有任何Pthread API属于异步信号安全函数,均无法在信号处理函数中安全加以调用。所以当多线程应用程序必须处理异步产生的信号时,通常不应该将信号处理函数作为接收信号到达的通知机制。推荐的方法如下:

  • 所有线程都阻塞进程可能接收的所有异步信号。最简单的方法是,在创建任何其他线程之前,由主线程阻塞这些信号。后续创建的每个线程都会继承主线程信号掩码的一份拷贝;
  • 再创建一个专用线程,调用函数sigwaitinfo()、sigtimedwait()或sigwait()来接收收到的信号
    这一方法的优势在于,同步接收异步产生的信号。当接收到信号时,专有线程可以安全地修改共享变量,并可调用非异步安全的函数。

线程与进程控制

与信号机制类似,exec()、fork()和exit()的问世均早于Pthread API,接下来的段落将指出在多线程程序中的使用此类系统调用所应关注的细节

线程和exec()

只要任一线程调用了exec()系列函数之一时,调用程序将被完全替换。除了调用exec()的线程之外,其他所有的线程都将立即消失。没有任何线程会针对线程特有数据执行结构函数,也不会调用清理函数。该进程的所有互斥量和属于进程的条件变量都会消失。

线程和fork()

当多线程进程调用fork()时,金会将发起调用的线程复制到子进程中。其他线程均在子线程中消失,也不会为这些线程调用清理函数以及针对线程特有数据的结构函数。将引起如下问题

  • 虽然只将发起调用的线程复制到子进程中,但全局变量的状态以及所有的Pthreads对象都会在子进程中得以保留
  • 因为并为执行清理函数和针对线程特有数据的结构函数,多线程程序的fork()调用会导致子进程的内存泄露。

线程与exit()

任何线程调用了exit,或者主线程执行了return,所有线程都将消失,也不会执行线程特有数据的结构函数以及清理函数。

线程实现模型

线程API的3中不同模型,这3中实现模型的差异主要集中在线程如何与内核调度实体(KSE)相映射。KSE是内核分配CPU以及其他系统资源的单位

多对一(M:1)实现

在M:1线程视线中,关乎线程创建,调度以及同步的所有细节全部由进程内用户空间的线程库来处理

一对一(1:1)实现

每个线程映射一个单独的KSE,内核分别对每个线程做调度处理,线程同步操作通过内核系统调用实现。

多对多(M:N)实现

M:N实现旨在结合1:1和M:1模型的优点
每个进程都有多个与之相关的KSE,并且也可以把多个线程映射到一个KSE。这种设计允许内核将同一应用的线程调度到不同的CPU上运行,同时也解决了随线程数量而放大的性能问题。

Linux POSIX线程实现

两种实现

  • LinuxThreads
  • NPTL (native POSIX Threads Library)

其中NPTL采用1:1模型设计,始于Linux 2.6

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值