文章目录
一、线程的概念及实现
1.1、线程的概念:
线程是进程内的一条执行路径或执行序列; 一个进程可以包含多条线程
1.2、线程的三种分类:
用户级:
创建开销小,由线程库直接管理, 无法使用多处理器资源
内核级:
创建开销大,由内核直接管理, 可以使用多处理器资源
组合模型:
既可以使用多处理器资源,又能创建多个
1.3、线程和进程的区别:
- 进程是资源分配的最小单位; 线程是CPU调度的最小单位
- 进程有自己独立的地址空间; 线程共享进程的地址空间
- 进程的创建消耗资源大; 线程的创建消耗相对较小
- 进程的切换开销大; 线程的切换开销较小
二、Linux系统,线程实现的方法
2.1线程库的接口介绍
头文件:#include <pthread.h>
1.创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void*(*start_routine)(void*), void *arg);
pthread_create(); 用于创建线程
thread:用于接收创建的线程的ID
attr:指定线程的属性;多为NULL
start_routine: 指定线程函数
arg: 给线程函数传递的参数; 成功返回0,错误返回错误码
2.退出线程
int pthread_exit(void* retval); //退出线程
pthread_exit(); 退出线程
retval: 指定退出信息
3.等待线程退出
int pthread_join(pthread_t thread, void ** retal);
pthread_join() 等待thread指定的线程退出,线程未退出时该方法阻塞
retal : 接收thread线程退出时,指定的退出信息
三、线程同步
线程同步是指当一个线程在对临界资源进行操作时,其他线程都不可以对该资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按照预定先后次序执行。
线程同步的四种方法:互斥锁、信号量、条件变量、读写锁
3.1互斥锁
1、概念: 用于保护关键代码段,确保在任一时刻只能有一个线程访问该资源,既保证在该时刻内其独占式的访问
2.互斥锁的基本API
以下这些函数成功时返回0,失败则返回错误码
-
初始化互斥锁
mutex: 指向要操作的目标互斥锁; 互斥锁的类型是 pthread_mutex_t 结构体
mutexattr: 指定互斥锁的属性。如果设置为NULL, 则表示使用默认属性
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* mutexattr);
PS: 也可以这样初始化互斥锁(静态化初始锁):
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
宏PTHREAD_MUTEX_INITIALIZER 实际上只是把互斥锁的各个字段都初始化为0
-
加锁
mutex: 同上, 指向要操作的目标互斥锁
pthread_mutex_lock 函数以原子操作的方式给一个互斥锁加锁。
若目标互斥锁已经被加锁, 则该方法的调用将阻塞, 直到该互斥锁的占有者将其解锁
int pthread_mutex_lock(pthread_mutex_t* mutex);
-
解锁
mutex: 同上,指向要操作的目标的互斥锁
pthread_mutex_unlock 函数以原子操作的方式给一个互斥锁解锁。若此时有其他线程正在等待这个互斥锁, 则这些线程中的某一个将获得它
int pthread_mutex_unlock(pthread_mutex_t* mutex);
-
销毁互斥锁
mutex: 同上,指向要操作的目标互斥锁
pthread_mutex_destory 函数用于销毁互斥锁。是释放其占用的内核资源。销毁一个已经加锁的互斥锁将导致不可预期的后果
int pthread_mutex_destory(pthread_mutex_t* mutex);
3.2 (POSIX)信号量
以下这些函数成功时返回0,失败则返回-1并设置errno
-
sem_init 函数
初始化一个未命名的信号量;
sem : 指向被操作的信号量
pshared 指定信号量的类型。若为0,表示这个信号量是当前进程的局部信号量, 否则该信号量就可以在多个进程之间共享。
value: 指定信号量的初始值(1 或 0)
若初始化一个已经被初始化的信号量将导致不可预期的结果
#include<semaphore.h>
int sem_init(sem_t* sem, int pshared, unsigned int value);
-
sem_wait 函数
以原子操作的方式将信号量的值 减1
若信号量的值为0,则该函数阻塞,直到这个信号量具有非0值
sem : 同上,指向被操作的信号量
int sem_wait(sem_t* sem);
-
sem_post 函数
以原子操作的方式将信号量的值 加1
当该信号量的值大于0时, 其他正在调用sem_wait 等待信号量的线程将被唤醒
sem : 同上,指向被操作的信号量
int sem_post(sem_t* sem);
-
sem_destory 函数
用于销毁信号量,释放其占用的内核资源
若销毁一个正在被其他线程等待的信号量,将导致不可预期的结果
sem : 同上,指向被操作的信号量
int sem_destory(sem_t* sem);
3.3 条件变量
条件变量提供了一种线程间的通知机制;当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程
(成功返回0; 失败返回错误码)
-
pthread_cond_init 函数
用于初始化条件变量
cond: 指向要操作的目标条件变量; 条件变量的类型:pthread_cond_t 结构体
cond_attr: 指定条件变量的属性; 若为NULL, 则使用默认属性
int pthread_cond_init(pthread_cond_t* cond, pthread_conattr_t* cond_attr);
-
pthread_cond_signal 函数
用于唤醒一个等待目标条件变量的线程
cond: 同上,指向要操作的目标条件变量
int pthread_cond_signal(pthread_cond_t* cond);
-
pthread_cond_broadcast 函数
以广播的方式唤醒所有等待目标条件变量的线程
cond: 同上,指向要操作的条件变量
int pthread_cond_broadcast(pthread_cond_t* cond);
-
pthread_cond_wait 函数
用于等待目标条件变量
cond: 同上,指向要操作的条件变量
mutex: 用于保护条件变量的互斥锁,确保pthread_cond_wait 操作的原子性
在调用该函数前,必须保证互斥锁 mutex 已经加锁;
该函数执行时,首先把调用线程放入条件变量的等待队列中,然后将互斥 mutex 解锁。当 pthread_cond_wait 函数成功返回时,互斥锁 mutex 将再次被锁上
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
-
pthread_cond_destory 函数
用于销毁条件变量, 释放其占用的内核资源;
销毁一个正在被等待的条件变量将失败并返回 EBUSY
3.4 读写锁
细化——锁; 分“读锁”、“写锁”
未加锁 读加锁 写加锁
读锁 Yes Yes No
写锁 Yes No No
成功返回0; 失败返回错误码
- 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t*rwlock, pthread_rwlockattr_t* attr);
- 读——加锁
int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
- 写——加锁
int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
- 解锁
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);
- 销毁锁
int pthread_rwlock_t_destory(pthread_rwlock_t* rwlock);
四、线程安全
多线程中无论调度顺序如何,最终的结果都是一样的、正确的。那么说这些线程是安全的
要点:
- 对线程同步,保证同一时刻只有一个线程访问临界资源
- 在多线程中使用线程安全的函数;安全的函数指:如果一个函数能被多个线程同时调用且不发生静态条件,则它是线程安全的
- 线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。