线程
线程是计算机中独立运行的最小单位。对用户来说的线程在linux内核看来就是的进程(轻量级进程),主线程和子线程共享内存空间,但是他们的pcb(内存控制块)是不同的。
线程被创建之后,地址空间没有发生生变化,主线程与子线程共享地址空间,但有独立的pcb
主线程与子线程不共享栈区,其他的区域共享。所以线程间通信可以使用全局变量,或者使用堆空间进行通信。
创建子线程时不复制内存空间,所以线程更节省空间。
1.1 创建线程
int pthread_create(
pthread_t *thread, //线程ID = 无符号长整形
const pthread_attr_t *attr, //线程属性,NULL
void *(*start_routine)(void *), //线程处理函数
void *arg //线程处理函数参数
);
- 参数:
- thread:传出参数,线程创建成功之后,会被设置一个合适的值
- attr:默认传NULL
- start_routine:子线程的处理函数
- arg:回调函数的参数
- 返回值:成功返回0,失败返回错误号,perror()不能使用该函数打印错误信息
1.2 单个线程退出
#include <pthread.h>
void pthread_exit(void *retval);
- retval:必须指向全局或堆
1.3 等待线程退出
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
- thread:要回收的子线程的线程ID
- retval:读取线程退出的时候携带的状态信息
1.4 线程分离
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 调用该函数之后不需要pthread_join
- 子线程会自动回首自己的pcb
1.5 取消线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
- 在要杀死的子线程对应的处理的函数内部,必须做过一次系统调用
- pthread_testcancel(); //设置取消点
2. 线程同步
不同的线程同事操作共享区域数据可能会出现混乱,为了解决这一问题,就有了线程同步来协同步调,让线程按照一定的顺序执行
线程同步要用到锁,条件变量。下面进行介绍。
2.1 互斥锁(互斥量)
在说锁之前,先说一下什么是原子操作。原子操作可以看做是CPU处理一个指令,在处理一个指令的时候,不会让别的指令执行。
1.互斥锁类型:
创建一把锁(定义互斥锁,全局变量):pthread_mutex_t mutex;
2.互斥锁的特点:
- 多个线程访问共享数据的时候是串行的
3.使用互斥锁缺点
- 串行,效率低
4.互斥锁使用的步骤
- 1.创建互斥锁:pthread_mutex_t mutex;
- 2.初始化:phtread_mutex_lock_init(&mutex, NULL); - - mutex = 1
- 找到线程共同操作的共享数据
- 3.加锁(操作共享资源之前加锁):
- pthread_mutex_lock(&mutex); //阻塞线程 - - mutex = 0
- pthread_mutex_trylock(&mutex); //如果锁上锁,直接返回,不阻塞,共享数据操作
- 4.解锁:pthread_mutex_unlock(&mutex); - - mutex = 1
- 阻塞在锁上的线程全部被唤醒
- 3.加锁(操作共享资源之前加锁):
- 5.销毁:pthread_mutex_dedtroy(&mutex);
如果要加锁,对所有操作共享数据的线程都要加锁
5.互斥锁相关函数:
- 初始化互斥锁:
pthread_mutex_init(
pthread_mutex_t *restrict mutex;
pthread_mutex_t *restrict attr
);
- 销毁互斥锁
pthread_mutex_dedtroy(pthread_mutex_t *mutex);
- 加锁
pthread_mutex_lock(pthread_mutex_t *mutex);
- mutex:没有被上锁,当前线程会将这把锁锁上
- 被锁上了,当前线程阻塞
- 锁被打开的时候解除阻塞
- 尝试加锁,失败返回,不阻塞
pthread_mutex_trylock(pthread_mutex_t *mutex);
- 没有锁上,当前线程会给这把锁加锁
- 如果锁上了:不会阻塞,返回
if(pthread_mutex_trylock(&mutex) == 0){
//访问共享资源
}
else{
//错误处理
//或再次尝试加锁
}
- 解锁
pthread_mutex_unlock(pthread_mutex_t *mutex);
2.2
造成死锁的原因
1.自己锁自己
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
操作完成之后,一定要解锁
2.假设现有线程1,线程2 | 共享资源A,共享资源B | 锁A,锁B。
- 线程1对共享资源A加锁成功,mutex_A加锁
- 线程2对共享资源B加锁成功,mutex_B加锁
- 线程1访问共享资源B时—>线程1阻塞
- 线程2访问共享资源A时—>线程2阻塞
解决:
1.让线程按照一定的顺序区访问共享资源
2.在访问其他锁的时候,需要现将自己的锁解开
3.使用trylock
3. 读写锁
1.读写锁是一把锁
- pthread_rwlock_t lock;
2.读写锁的类型:
- 读锁 - 对内存做读操作
- 写锁 - 对内存做写操作
3.读写锁的特性:
- 线程A加读锁成功,又来了三个线程,做读操作,可以加锁成功
- 读共享 - 并行处理
- 线程A加写锁成功,又来了三个线程,做写操作,三个线程阻塞
- 写独占
- 线程A加读锁成功,又来了B线程加写锁阻塞,又来了C线程加读锁阻塞
- 读写不能同时
- 写的优先级高
5.读写锁的适用场景:
- 互斥锁 - 读写串行
- 读写锁:
- 读:并行
- 写:串行
- 程序中的读操作 > 写操作的时候
6.主要操作函数:
- 初始化读写锁:pthread_rwlock_init();
- 销毁读写锁:pthread_relock_destory();
- 加读锁:pthread_rwlock_rdlock();
- 尝试加读锁:pthread_rwlock_tryrdlock();
- 加写锁:pthread_rwlcok_wrlock();
- 尝试加写锁:pthread_rwlcok_trywrlock();
- 解锁:pthread_rwlock_unlock();
7.读写锁,互斥锁
- 阻塞线程
- 不是什么时候都能阻塞线程
4. 条件变量
1.条件变量是锁吗?
- 不是锁,但是条件变量能后阻塞线程
- 使用条件变量 + 互斥锁
- 互斥量:保护一块共享数据
- 条件变量:引起阻塞
- 生产者和消费者模型
2.条件变量的两个动作?
- 条件不满足,阻塞线程
- 当条件满足,通知阻塞的线程开始工作
3.条件变量的类型:pthread_cond_t;
4.主要函数
- 初始化一个条件变量 - cond
phread_cond_init(
pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr
);
- 销毁一个条件变量
pthread_cond_destroy(pthread_cond_t *cond);
- 阻塞等待一个条件变量
pthread_cond_wait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex
);
阻塞线程
将mutex解锁
该函数解除阻塞,会对互斥锁加锁
- 限时等待一个条件变量(阻塞一定的时间)
phtread_cond_timedwait(
pthread_cond_t *restrict cond,
pthread_mutex_t *restict mutex,
const struct timespec *restrict abstime
);
- 唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t *cond);
- 唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t *cond);
5. 信号量(信号灯)
1.头文件 - semaphore.h
2.信号量类型
- sem_t sem;
- 加强版的互斥锁
3.主要函数:
- 初始化信号量
sem_intit(sem_t *sem, int pshared, unsigned int value);
0 - 线程同步
1 - 进程同步
value - 最多有几个线程操作共享数据
- 销毁信号量
sem_destroy(sem_t *sem);
- 加锁 - -
sem_wait(sem_t *sem);
- 调用一次相当于对sem做了 -- 操作
- 如果sem值为0,加锁失败,不阻塞,直接返回
- 限时尝试加锁
sem_trywait(sem_t, *sem);
- 解锁 ++
sem_post(sem_t *sem); //对sem做了++操作