一. 线程概念
1.线程
典型的UNIX进程可以看成只有一个控制线程:一个进程在某个时刻只能做一件事
故,线程是一个进程内部的的控制序列
2.进程和线程
·进程是资源竞争的基本单位
·线程是程序执行的最小单位
·一个进程中的线程共享同一份资源,包括同一地址空间,文件买哦首付与表,每种信号的处理方式,当前工作目录,用户ID和组ID
·线程也有自己数据:线程ID, 一组寄存器, 栈, 调度优先级和策略, 信号屏蔽字, errno变量,以及线程的私有数据
3.线程的优点
·创建一个线程比创建一个进程的代价小许多
·与切换进程相比,线程之间的切换更方便
·线程比进程占用的资源少
·可以充分利用多处理器的并行操作
·线程之间有方便的通信机制,同一进程下,一个线程的数据可以直接被其他线程使用,快捷方便
·在等待慢速I/O操作结束时,程序可以执行其他的计算任务
·计算密集型应用,为了能再多处理器系统中运行,将计算分解到多个线程中实现
·I/O密集型应用为了提高性能,将I/O操作重叠,多个线程可以等待不同的I/O操作
4.线程的缺点
·性能损失,线程有额外的同步和调度的开销,但是可用的资源不变
·代码鲁棒性降低,线程之间可能会因为共享资源而造成不良的影响,导致代码的健壮性不强
·缺乏访问控制,进程是访问控制的基本粒度,线程调用某些OS函数会造成影响
·编程难度提高了(确实)
5.线程ID
·在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL.在这种实现下,线程又被称为轻量级进程,每一个用户态的线程,在内核中都有对应的一个调度实体,也拥有自己的进程描述符
·没有线程之前,一个进程有一个进程ID,但是有了线程之后,一个用户进程管辖N个用户态线程,每个线程作为独立的调度单位在内核中都有自己的进程描述符(task_struct),进程和内核的描述符变为了1:N的关系
由于以上原因,Linux引入了线程组的概念
struct task_struct {
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;
...
struct list_head thread_group;
...
};
·多线程的进程又被称为进程组,线程组内的每一个线程在内核之中都存在一个进程描述符与之对应.
| 用户态 | 系统调用 | 内核进程描述符对应的结构 |
| 线程ID | pid_t gettid(void) | pid_t pid |
| 进程ID | pid_t getpid(void) | pit_t tid |
所以,进程描述符中的ID其实是Thread Group ID,意思是用户层面的ID,线程ID是一个进程ID
如果需要查看线程ID,可以通过ps命令,加上-L选项,会出现如下信息:
·LWP:线程ID,调用gettid()系统调用时的返回值
·NLWP:线程组内线程的个数
Linux提供了gettid系统调用来返回其线程ID,可以这个函数没有被封装起来,并没有提供接口给我们
所以我们获取线程ID时使用如下方法:
#include <sys/syscall.h>
pid_t tid;
tid = syscall(SYS_gettid);
在Linux中,存在一个线程ID和线程组ID(进程)相等,该线程就是想线程组的主线程,主线程在内核中被称为group_leader
内核在创建第一个线程时,会把线程组的ID设置成第一个线程的ID,group_leader则指向自身
线程组其他的线程ID由内核分配,其线程组ID总是和主线程ID相同
注意,线程和进程不一样,进程有父子进程的概念,而一个线程组中所有的线程都是对等的关系
6.线程ID和进程地址空间
·线程控制时,调用pthread_create创建一个线程,其线程ID存储在这个函数的第一个参数之中. 这种ID和前面说的线程ID不一样
·上面提到的线程ID是属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小的单位,所以需要一个数值来表示该唯一的线程,所以gettid获取的是内核中的线程ID,pthread_self获取的是POSIX中定义的线程ID,这种ID只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以用来复用原来的ID
·线程库实际上由两个部分组成:内核的线程支持和用户态的线程支持,Linux早期内核不支持线程的时候glibc就在库中支持多线程了,其实就是用户态多线程.
·Linux上的线程实现就是在内核支持的基础上以POSIXthread的方式对外封装了接口,所以才会有两个线程ID
pthread_self函数:
pthread_t pthread_self(void);
pthread_t的类型取决于实现,对于Linux目前实现的NPTL来说,pthread_t类型的线程ID本质上是一个进程地址空间上的地址

二.线程控制
1.创建线程
与线程有关的函数构成一个完整的序列,使用时引入头文件pthread,编译时要加上-lthread链接静态库
//函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void* (*star t_routine)(void*), void *arg);
/*
**参数
**thread:返回线程ID
**attr:设置线程的属性,attr为NULL表⽰示使⽤用默认属性
**start_routine:是个函数地址,线程启动后要执⾏行的函数
**arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码
**返回值:成功返回0;失败返回错误码
*/
pthreads函数出错时不会设置errno变量,所以对于线程的错误,建议通过返回值判定
2.线程终止
终止线程而不终止进程时,有三种方法:
·从线程函数return,对于主线程而言,调用return相当于调用exit
·线程调用pthread_exit终止自己
·同组的其他线程通过pthread_cancel可以z终止当前线程
pthread_exit函数:
功能:线程终⽌止
原型
void pthread_exit(void *value_ptr);
参数
value_ptr:value_ptr不要指向⼀一个局部变量。
返回值:⽆无返回值,跟进程⼀一样,线程结束的时候⽆无法返回到它的调⽤用者(⾃自⾝身)
pthread_cancel函数:
功能:取消⼀一个执⾏行中的线程
原型
int pthread_cancel(pthread_t thread);
参数 thread:线程ID 返回值:成功返回0;失败返回错误码
3.线程等待与分离
·线程等待
为什么需要线程等待?已经退出的线程,其空间没有被释放,创建新的线程时不会复用刚才退出线程的空间
pthread_join函数:
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向⼀一个指针,后者指向线程的返回值 返回值:成功返回0;失败返回错误码
调用这个pthread_join函数的线程将阻塞的等待id为thread的线程终止,thread以不同的方式终止,得到的终止方式也不同
·如果是return返回,value_ptr存放的是线程函数的返回值
·如果被别的线程调用pthread_cancel函数终止掉,value_ptr里存放的是PTHREAD_CANCELED
·如果thread线程调用pthread_exit终止,则value_ptr存放的是传给pthread_exit的参数
如果对线程的返回状态不感兴趣,则可以把value_ptr置为NULL

·线程分离
默认情况下,新创建的线程是joinable(可结合)的,线程退出后,需要调用pthread_join函数来进行线程等待,否则无法释放资源
如果不关心线程的返回状态,则pthread_join函数并没有什么用,反而浪费资源(有个线程在阻塞式的等待),所以就用到了线程分离
pthread_detach函数:
int pthread_detach(pthread_t thread);
举个栗子:

一定要先等一会儿,确保新创建的线程执行了
本文介绍了线程概念和线程控制相关知识。线程是进程内部的控制序列,是程序执行的最小单位,有优点也有缺点。还阐述了线程 ID 的相关内容,包括其在 Linux 中的实现和获取方法。在线程控制方面,讲解了线程的创建、终止、等待与分离等操作。

859

被折叠的 条评论
为什么被折叠?



