1. 概念
1.1 为什么要用线程
-
由于进程的地址空间是私有的,多个进程在运行的时候是上下文切换执行的,如果要从一个进程切换到另一个进程执行,那么就需要将之前进程的用户空间切换到新的进程,由于用户空间有3G,所以切换起来是比较消耗资源,系统开销比较大
-
为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程,多个线程共用同一个进程的资源,所以线程间来回切换时不用消耗太多资源,所以如果一个程序要执行多个不同任务,会选择使用多线程来实现
在同一个进程中创建的线程共享该进程的地址空间
Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度
1.2 线程与进程的关系
- 在操作系统设计上,从进程演化出线程,主要为了更好地支持多处理器,减少进程上下文切换的开销
- 线程属于进程,线程运行在进程空间内,同一进程所产生的线程共享同一用户内存空间,当进程退出时,进程所产生的线程都会被强制退出并清除。
- 一个进程至少需要一个线程作为它的指令执行体
- 进程管理着资源(cpu、内存、文件等),将线程分配到某个cpu执行。
- 进程是系统资源管理的最小单位,线程是程序执行的最小单位
- 每个进程都有自己的数据段、代码段和堆栈段。线程是轻型的进程,有独立的栈和
CPU
寄存器状态,线程是进程的一条执行路径,每个线程共享其所附属进程的所有资源,包括打开的文件、内存页面、信号标识以及动态分配的内存等 - 线程和进程比起来较小, 线程会花费更少的CPU资源
1.3 主控线程
默认情况下,一个进程中只有一个线程,称为主控线程
由主控线程创建的线程称之为子线程,不管是主控线程还是子线程,都附属于当期的进程
1.4 编译 含有线程的程序
多线程通过第三方线程库实现
线程库 libpthread.so
手动链接库文件 gcc xxx.c -lpthread
2. 线程相关函数
2.1 pthread_create() 创建一个子线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:
创建一个子线程
参数:
thread:创建的线程的线程号
attr:线程的属性,一般设置为NULL表示默认属性
start_routine:线程处理函数
arg:给线程处理函数传参的
返回值:
成功:0
失败:非0
2.1.1 线程间调度机制
线程的调度机制 时间片轮转,上下文切换
注意:虽然线程的处理代码就是一个函数,也满足函数的性质,但是这个函数的执行顺序与一般的普通函数完全不同
子线程的执行代码只是在对应的线程处理函数里面,函数执行结束,当前线程也就结束了,如果线程所在的进程在某一时刻结束,那么整个进程中所有的线程都会强制清除
2.1.2 线程多资源使用问题
多线程共享同一个进程的资源,例如全局变量,这个进程中的线程都可以去操作,但是需要考虑资源抢占的问题
每一个线程的执行就是一个函数,所以线程的资源与普通函数几乎没有区别,控线程的数据可以通过pthread_create
的最后一个参数传递给线程处理函数
2.2 pthread_self() 获取自己的线程号
#include <pthread.h>
pthread_t pthread_self(void);
功能:
获取调用者的线程号
参数:
无
返回值:
成功:本线程的线程号
本函数总是执行成功。
2.3 pthread_exit() 退出本线程
#include <pthread.h>
void pthread_exit(void *retval);
功能:
在指定线程中调用,用于退出一个线程
参数:
retval:线程退出时返回的值,可以被其他同进程的线程调用pthread_join接收
返回值:
无
注意:主控线程结束,进程是没有结束的,所以子线程还是可以继续运行的,但是进程如果结束,当前进程中所有的线程都会立即结束
2.4 pthread_cancel() 向指定线程发送结束请求
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
向指定的线程发送结束请求
参数:
thread:接收请求的线程,默认接收到请求线程就会结束
返回值:
成功:0
失败:非0
2.5 pthread_join() 阻塞等待线程结束
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
阻塞等待线程的结束
参数:
thread:指定要等待的线程的id
retval:线程结束后的状态值,主要接收pthread_exit所设置的值
返回值:
成功:0
失败:非0
2.6 pthread_detach() 将线程设置为分离态
线程的分离态和结合态
linux线程执行和windows不同,pthread有两种状态:结合态(joinable)或者是分离态(detached),线程默认创建为结合态。
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是detached状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
将线程设置为分离态
参数:
thread:线程id
返回值:
成功:0
失败:非0
如果将线程设置为了分离态,pthread_join就无法使用了
3. 线程的同步与互斥机制
3.1 概念
互斥:同一时间只能有一个线程执行,执行完毕后其他线程再执行
线程中的互斥机制 → 斥锁
同步:在互斥的基础上有顺序执行
线程中的同步机制 → 信号量 条件变量
学习线程间同步与互斥机制就是要解决多个线程对共一个共享资源操作时可能会产生的问题,将多线程对同一个资源操作抢占问题称之为竞态
3.2 互斥锁 运行机制
多个线程如果要对同一个共享资源进行操作,谁抢占到资源,就对共享资源的操作
进行上锁,执行操作,其他线程需要等待该线程解锁后,才能继续争抢对资源操作的权限。
这个锁或者操作权限就是互斥锁。
互斥锁,锁的是对资源的操作权限。
操作之前上锁,操作完毕后解锁,解锁之后其他线程又可以操作,以此类推
3.2.1 pthread_mutex_init() 初始化一个互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);
功能:
初始化一个互斥锁
参数:
mutex:互斥锁
mutexattr:互斥锁的属性,设置为NULL表示默认属性
返回值:
成功:0
3.2.2 pthread_mutex_lock 上锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
上锁
参数:
mutex:互斥锁
返回值:
成功:0
失败:非0
3.2.3 pthread_mutex_unlock 解锁
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
解锁
参数:
mutex:互斥锁
返回值:
成功:0
失败:非0
3.2.4 pthread_mutex_destory 销毁互斥锁
#include <pthread.h>
int pthread_mutex_destroy(