线程
- 进程:运行中的程序,资源分配的基本单位
- 线程:进程内部的一条执行路径,调度/执行的基本单位
- 实现
- 用户级:一条执行路径(内核)模拟三条执行路径(用户)
开销小,但无法使用多个处理器 - 内核级:内核支持,开销较大,内核直接管理,利用多处理器
- 组合/混合模型:两个组合,多线程,利用多处理器
- 用户级:一条执行路径(内核)模拟三条执行路径(用户)
- 实现
Linux:内核级,将所有线程都当做进程来实现,将线程视为与其他进程共享某些资源的进程,每个线程都有自己的PCB。(Windows具有支持线程的机制)
多线程
并发执行(交替,同时)
多核,拆分
#include<pthread.h>
//创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
//thread:保存创建线程的ID
//attr:指定线程属性。NULL
//start_routine:线程函数指针
//arg:传递线程函数的参数
//返回值:0,成功
//退出线程
void pthread_exit(void *retval);
//retval:用于退出线程时返回信息
//等待指定线程退出,阻塞
int pthread_join(pthread_t thread, void **retval);
//thread: pthread_create创建线程的id
//retval: 接收pthread_exit退出线程时返回的信息
编译链接时需要指定库: -pthread.
主线程运行时间短,其他线程还未运行,整个进程直接结束
所有线程同时访问一个资源,冲突,指令(多核并行)
线程同步
- 信号量
#include <semaphore.h>
//初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
//sem:保存初始化信号量的地址
//pshared:进程间共享;0,否
//value:初始值
//P操作
int sem_wait(sem_t *sem);
//sem:保存信号量的地址
//V操作
int sem_post(sem_t *sem);
//sem:保存信号量的地址
//销毁信号量
int sem_destroy(sem_t *sem);
//sem:保存信号量的地址
- 互斥锁
相当于初始值为1的信号量
#include <pthread.h>
//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//mutex: 保存初始化互斥锁的地址
//attr: 指定互斥锁属性,NULL
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//mutex: 保存初始化互斥锁的地址
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//mutex: 保存初始化互斥锁的地址
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//mutex: 保存初始化互斥锁的地址
生产者/消费者模型
有限缓冲问题
- 多线程共享同一个缓冲区,生产者线程添加数据,消费者线程读取数据(管道?)
- 互斥的使用缓冲区
- 缓冲区空,不能读取
- 缓冲区满,不能添加
- 解耦:不直接互相调用,不影响对方,将两者之间的强耦合解开,变成三者之间的弱耦合
- 支持并发:不需要等待对方操作,并发执行
- 支持忙闲不均:双方直接操作速度不均衡浪费CPU时间片,加入缓冲区达到动态平衡
条件变量
线程之间同步共享数据的值
通知机制:
当某个共享数据的值达到某个值时,唤醒等待该数据的线程
#include <pthread.h>
//初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//cond:保存条件变量的地址
//attr: 设定条件变量的属性
//唤醒等待条件变量的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
//唤醒一条线程
int pthread_cond_signal(pthread_cond_t *cond);
//cond: 条件变量
//将某个线程加入到等待条件变量的队列
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//cond: 条件变量
//mutex: 互斥锁,用于保护条件变量,确保wait操作的原子性
// wait过程:在外部先加锁,waite内先解锁,然后加入队列,阻塞并休眠,当唤醒时,首先加锁,然后执行线程的程序,最后考虑在外部进行解锁
// 此过程期间唤醒操作无法改变条件变量队列,考虑在唤醒操作前后添加锁操作,保护条件变量;当wait能够加锁时,才得以成功唤醒
// (多核多线程)当存在多线程同时等待某个条件时,需要进行条件判断(while),以保证只有一个进程进入临界区,避免唤醒异常,重新进入阻塞状态
int pthread_cond_destroy(pthread_cond_t *cond);
//cond: 条件变量
读写锁
- 允许同时读
- 不允许同时写,同时读写
#include <pthread.h>
//读写锁初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
//rwlock: 保存初始化读写锁的地址
//attr:指定读写锁属性,NULL
//销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//读锁,允许多个线程进程读取操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//写锁,只允许一个进程进行写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//rwlock: 读写锁
线程安全:多线程程序的结果不受调度(线程)顺序的影响
- 同步:信号量,锁
- 线程安全函数,可重入函数(一个函数被多线程同时调用结果不出现异常)
- 静态/全局变量,多线程不安全
g++ thread_C++.cpp -o thread_C++ -pthread
man帮助手册下载
wget https://mirrors.edge.kernel.org/pub/linux/docs/man-pages/man-pages-5.13.tar.gz
tar -xzvf man-pages-5.13.tar.gz
cd man-pages-5.13
make
make install
多线程与fork
- fork复制进程中的所有线程(所有资源),但只启用一条执行路径,即:启用fork所在的执行路径(线程)
- fork,父子进程使用各自的锁,但子进程会复制父进程锁的状态
#include <pthread.h>
//指定fork前后调用的进程
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
//prepare:fork之前执行的程序
//parent:父进程中执行的程序
//child: 子进程中执行的程序