一:线程
1.线程的相关概念:
线程是比进程粒度更细的执行流
理解:
(1) 每一个线程的数据代码都是目标进程的子集
(2)线程于线程数据和代码大部分都共享
(3)线程的创建和释放成本非常低
(4)线程的调度成本很低线程是在进程内部运行的执行流
- Linux下没有真正的线程,是用进程模拟线程(操作系统没有专门为线程创建对应的结构体),Linux下的线程叫轻量级进程。
- 线程在进程的地址空间运行
- 线程是cpu调度的基本单位
- 进程是承担系统分配资源的基本单位
2.线程私有的和多个线程共享的数据
- 每个线程私有的数据:线程ID、一组寄存器(保存自己独立的上下文信息)、栈(私有栈)、信号屏蔽字、调度优先级。
- 进程内部多个线程共享的数据:文件描述符、每种信号的处理方式、用户ID和组ID
3.线程的优点
- 创建一个新县城的代价要比创建一个进程小很多。
- 与进程的切换相比,线程之间的切换需要操作系统做的事情少很多
- 线程占用的资源要比进程少很多。
- 能充分利用多处理器的可并行数量
4.线程的缺点
- 性能损失
- 健壮性降低
- 缺乏访问控制
- 编程难度提高
二:线程控制:
1.POSIX线程库(用户级线程库):
- 因为Linux下是没有真正的线程,所以系统是没有对应的接口。
- 就有人开发了POSIX线程库,使用这些库函数,要引用头文件pthread.h
- 链接这写线程函数库时要使用编译器命令的“-lpthread”选项(链接库)
2.创建线程:
- int pthread_create(pthread_t thread, const pthread_attr_t *attr, void (start_routine) (void ), void *arg); //第一个参数为线程ID(库提供的ID),第二个参数设置线程的属性(NULL表示默认属性),第三个参数是函数地址,线程启动后要执行的函数,最后一个参数是传给线程启动函数的参数。返回0表成功,出错返回错误码
3.线程等待(主线程等待):
- int pthread_join(pthread_t thread, void **retval);//阻塞式等待,第一个参数为所等的线程ID,第二个参数为输出型参数,获得所等待线程的退出信息。
- 注:已经退出的线程,其空间没有被释放仍然在进程的地址空间内,创建新的线程不会复用刚才退出线程的地址空间,所以需要等待。线程退出不可能是异常退出,因为异常退出的话,整个进程就挂了,没机会等待了。
4.线程终止:
- 三种终止情况
(1)代码跑完,结果正确
(2)代码跑完,结果不正确
(3)异常退出。 在运行期间,只要有一个线程出错异常退出,全部线程(进程)就会挂掉。 - 只终止某个线程而不是整个进程的方法:
(1)线程调用exit就是进程调用exit,就是进程退出。
(2)主线程调用return是指进程退出,而线程调用指线程退出
(3)pthread_exit(void *retval);是线程退出,主线程退出就是进程退出,不能调用pthread_exit.
(4)pthread_cancel(pthread_t thread);//取消某个线程,也可以取消自己。
5.线程ID及地址空间布局
- 多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID;进程描述中的tgid,含义是Thread Group ID, 该值对应的是用户层面的进程ID。
- 这里的PID就是getpid的返回值,LWP就是gettid()的返回值,是在系统级别内有效。
pthread_t pthread_self(void); //这里返回的线程ID,属于NPTL线程库的范畴,只在当前库下有效。实质上就是动态库对应线程对应的起始地址。
注: Linux提供了gettid系统调用来返回其线程ID,可是glibc并没有将系统调用封装起来,在开放接口来共程序员使用,如果想要获取线程ID,可采用如下方法:
- 进程地址空间布局:
6.线程分离:
- 默认情况下,新创建的线程是joinable(可结合的),线程退出后,需要对其进行等待。如果不关心线程的返回值,join就是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
- int pthread_detach(pthread_t thread);//线程组内的其他线程对目标线程进行分离,也可以是线程自己分离。