Linux 线程基础

什么是线程 什么是进程

线程是计算机中运算调度的最小单位,
他在进程的地址空间内运行。 是进程实际运作单位
一条线程是进程中单一顺序的执行流
。线程使操作系统调度执行的基本单位。

而进程是操作系统中资源分配的基本单位,进程有自己独立的地址空间(页表),文件描述符管理的文件,IO设备,操作系统通过这些一定程度防止进程间资源的冲突。

???为什么vfork 保证子进程先执行 因为公用同一地址空间 父子进程同时在一个调用栈里跑 调用栈会混乱

内核角度看线程

在操作系统内核眼中 不区分进程和线程 他的眼中只有task_struct 要执行一个线程 (轻量级进程)/进程(单进程进程 或 主线程) 将其task_struct放入执行队列中

把一个线程挂起 就是把他放在挂起队列中

每一个进程有多个线程 也就是有多个PCB (task_struct) 内核通过task_struct来控制调度线程/进程

getpid() 返回的是task_struct结构体中的 pid_t tgid; 一个进程中的多个线程叫一个线程组 组id tgid
gettid() 返回值是task_struct结构体中的 pid_t pid

一个进程执行后 默认打开了第一个线程叫主线程 主线程pid tgid 一样 单线程进程中 只有主线程

线程组中除了主线程之外 其他线程是对等的 没有类似进程的父子关系.
线程组id等于进程中第一个线程id(主线程id)。tgid。

创建一个线程内核中多了一个县城的PCB,但是这个PCB和其进程组内的其他线程共用一份地址空间(页表)。
这里写图片描述

线程间的公有和私有

线程之间的 共享:
共享虚拟地址空间 数据段 代码段 堆 共享区 命令行参数和环境变量 (本质上是共享页表) 虚拟地址空间映射到同一物理地址
代码段也是共享的 只不过不同的线程跑的那个入口函数不一样 主线程也可以跑 其他线程的代码
文件描述符表 每种信号的处理方式 或自定义的信号处理函数 (本质上是 每一个线程的PCB中指向handler表的指针相同)

线程之间的私有
进程间应为各自独立的虚拟地址空间 和页表 使得进程间通信麻烦
调用栈 每个线程有自己的调用找 (主要是为了线程之间各自跑各自的不要互相干扰)
***这个栈 不是权限的限制 他的意思是 两个线程在同一虚拟地址空间的栈上各自拥一份调用栈 但是每个线程可以访问修改其他线程栈上的空间 只要这个空间是合法的。

线程的上下文 (本质上是当前线程执行时推出的时 寄存器的PC指针 存到线程的PCB)
线程ID
储存错误码的变量 errno
信号屏蔽字 (各自线程PCB中指向 各自不同的block表)
调调度度优先级
线程是调度的最小单元

线程的经典运行场景

为什么linux搞一个轻量级进程 因为为了实现任务调度的时候比较简单 内核代码的复用。

计算密集型应用 把计算问题分成几个部分 让多个核一起跑

I/O密集型应用 按下按钮的一瞬间 立刻创建一个多线程 让计算和I/O处理在时间上重叠。

进程线程的优劣

多线程对于多进程的好处:
创建 销毁 线程比创建进程的成本低得多
线程间切换比进程间切换成本低

为什么 : 线程的共用 。。。。。 不需要创建进程时大量的内存拷贝 内存释放 等等

坏处:
多线程的代码
调用调试难度提高 保证各个线程按照预期的顺序执行 同步.
对各个线程 的公共资源操作的互斥
一个线程崩溃 会导致整个进程都终止 一个线程调用一些系统调用会影响整个进程 signal() exit()

进程线程不是越多越好
看cpu资源 有几个核。处理速度。

使用命令查看线程

1、 ps -eLf 查看当前操作系统中的所有线程。
2、 pstack 查看某个进程包含的线程以及线程的调用栈。
3、 gdb into thread查看进程中的所有线程, thread +【线程id】进入该线程,bt查看线程的调用栈。

ps -eLf | head -1 && ps -eLf | grep a.out

POSIX线程库

因为Linux内核并不区分进程和线程,所以Linux没有关于线程的系统调用。
POSIX 线程库: 将操作进程的系统调用封装成线程库。

想得到内核中的线程id 可以通过 pid_t tid = syscall(SYS_getttid);
主线程的tid等于进程id
线程中的group_leader指针指向主线程。

1、创建线程。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

pthread_t    pthread_self();

用该函数创建线程之前需要先定义pthread_t 类型的线程id。
这个线程id和线程PCB中的tid不是一个, tid是为了内核调度使用,pthread_t 是用户在POSIX线程库中使用的

pthread_create()函数第一个参数值pthread_t*类型的指针,第二个参数设置线程的属性,默认填NULL,第三个参数填线程入口函数的地址,第四个参数是允许的向线程入口函数中传的一个参数。成功返回0,失败返回错误码。

2、 线程终止

  1. 线程入口函数调用return。(不适用于主线程)
    2、线程调用pthread_exit ()函数终止自己
    3、线程调用pthread_cancel()函数终止同一进程的其他线程
 #include <pthread.h>
 void pthread_exit(void *retval); 
 int  pthread_cancel(pthread_t id);      

retval指针指向一个全局变量。 或堆空间。返回线程的处理结果。
id是要让哪个线程退出。

3、线程等待与分离

因为线程退出的时侯,线程PCB保留了线程的退出状态,有时需要被读取,有其他线程接收退出了的线程的PCB,他才会被释放否则内存泄漏。

int pthread_join(pthread_t thread, void **retval);

该函数负责挂起等待thread线程,  retval指向被等待线程的推出状态或返回值。
不关心可以设置为NULL。

int  pthread_detach(pthread_t   thread)l

如果一个线程并不关心某线程的返回值就用该函数进行线程分离,被分离的线程可以自己退出时自己清理自己的线程资源。

线程共享地址空间和栈上的变量。
代码:
https://github.com/xym97/Linux/tree/master/thread/thread2.c
https://github.com/xym97/Linux/tree/master/thread/thread3.c

关于多线程提高效率

CPU的核心数和处理速度有限。
多线程可以充分利用CPU资源,提高CPU利用率。
但是线程不是越多越好, 多线程在一起会竞争同一个锁效率不一定高,
关键还是看CPU资源够不够。
如果是I/O密集型任务, 速度瓶颈主要在I/O上不在CPU上。

代码:
https://github.com/xym97/Linux/blob/master/thread/thread4.c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章