一、线程的同步与互斥
1. 互斥锁
(一)引入
多个进程都要去访问同一个资源(共享资源),需要去协调
多个线程去访问同一个资源也存在这个问题
死锁: 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
产生死锁两种常见原因:
①线程上锁以后忘记解锁了,导致自己无法再次上锁,其他线程也不能上锁
②线程上锁后没来得及解锁,线程就被取消了
特点:
①多个线程使用同一把锁,一个线程上锁了,那么其他线程上锁的时候就上不了,会被阻塞
②上锁操作跟解锁操作配合使用的,如果只有上锁,没有解锁,就会死锁
(二)相关的接口函数
(1)初始化互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
参数:mutex --- 互斥锁变量
mutexattr --- 互斥锁的属性,默认设置为NULL,使用系统默认的属性
(2)锁的基本操作(重点)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); //上锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
参数:mutex --- 互斥锁变量
(3)锁的销毁
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:mutex --- 互斥锁变量
2.条件变量
(一)引入
在开发过程中必须配合互斥锁一起来使用
条件变量是指互斥锁在上锁执行代码的时候,发生某个条件的时候,条件变量可以阻塞当前线程,或者条件变量也可以解除某个线程的阻塞
注意:所谓的发生某个条件,使用的时候根据实际情况去写条件
(二)相关的接口函数
(1)初始化条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
参数:cond --- 条件变量
cond_attr --- 条件变量的属性,一般设置为NULL,表示默认属性
(2)条件变量的使用
阻塞条件变量
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:cond --- 条件变量
mutex --- 互斥锁变量
特点: 先解锁然后阻塞当前线程(pthread_cond_wait函数有两个功能)
唤醒条件变量(解除阻塞)
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond); //唤醒其中一个线程
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有的线程
参数:cond --- 条件变量
特点: 上锁然后解除当前线程的阻塞
(3)条件变量的销毁
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
参数:cond --- 条件变量
3.线程中的信号量
原理:跟进程间通信的信号量一模一样
总结:linux中有三种信号量
SYSTEM-V IPC信号量 进程间
POSIX无名信号量 线程间
POSIX有名信号量 进程间
(一)POSIX无名信号量
(1)创建无名信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value); //semget()和semctl( SETVAL)
参数: sem --- 信号量变量
pshared --- 0 表示这个信号在线程间使用
非零 表示在进程间使用
value --- 信号量的值
(2)PV操作
#include <semaphore.h>
int sem_wait(sem_t *sem); //p操作,默认只能减1
int sem_post(sem_t *sem); //v操作,默认只能加1
参数: sem --- 信号量变量
(3)销毁无名信号量
#include <semaphore.h>
int sem_destroy(sem_t *sem);
参数: sem --- 信号量变量
(二)POSIX有名信号量
(1)创建有名信号量
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag); //打开有名信号
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); //新建有名信号量
返回值:成功返回新建好的有名信号量
参数:name --- 你想打开或者新建的那个有名信号量的名字
oflag --- O_CREAT and O_EXCL
mode --- 权限 0777
value --- 有名信号量的值
(2)PV操作
#include <semaphore.h>
int sem_wait(sem_t *sem); //p操作,默认只能减1
int sem_post(sem_t *sem); //v操作,默认只能加1
参数: sem --- 信号量变量
(3)关闭有名信号量
#include <semaphore.h>
int sem_close(sem_t *sem); //关闭
int sem_unlink(const char *name); //删除有名信号量
参数: sem --- 信号量变量
name --- 你想删除的那个有名信号量的名字
注意:
第一个:只要你新建了有名信号量,默认在ubuntu的/dev/shm/里面会生成一个有名信号量文件,文件名字 sem.xxxx(xxxx是你代码取的名字)
二、配合解决死锁的第二种原因
1. 第一种原因:
解决方法: 自己把解锁的代码加上即可
2.第二种原因:
解决方法:
(一)方法一
把线程设置为不可以取消
(二)方法二
调用linux中提供
#include <semaphore.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg); //当线程被取消的时候,这个函数会被自动调用
void pthread_cleanup_pop(int execute);
参数:void (*routine)(void *) --- 函数指针
arg --- 传递给routine的参数
execute --- 0 表示不执行routine指向的函数(线程被取消才执行)
非零 表示执行routine指向的函数(线程被不被取消都执行)
注意:push和pop两个函数必须成对出现,只用一个会编译出错
三、线程池
1.概念
由多个线程组成的集合称为线程池
线程池创建的目的是为了并发执行任务
2.相关设计
(一)相关结构体
struct task
{
void *(*task)(void *arg);
void *arg;
struct task *next;
};
(二)相关接口函数
(1)线程池初始化
#include <thread_pool.h>
bool init_pool(thread_pool *pool,unsigned int threads_number);
返回值:成功返回true,失败返回false
参数:pool --- 线程池指针
threads_number --- 初始活跃线程个数(大于等于1)
(2)投送任务
#include <thread_pool.h>
bool add_task(thread_pool *pool,void *(*do_task)(void *arg),void *arg);
返回值:成功返回true,失败返回false
参数:pool --- 线程池指针
do_task --- 投送至线程池的执行例程
arg --- 执行例程 do_task的参数,不需要则为NULL
(3)增加活跃线程
#include <thread_pool.h>
int add_thread(thread_pool *pool,unsigned int additional_threads);
返回值:>0 --- 实际新增线程个数
-1 --- 失败
参数:pool --- 需要增加线程的线程池指针
additional_threads --- 新增线程个数
(4)删除活跃线程
#include <thread_pool.h>
int remove_thread(thread_pool *pool,unsigned int removing_threads);
返回值:>0 ---当前线程池剩余线程个数
-1 --- 失败
参数:pool --- 需要删除的线程的线程池指针
removing_threads --- 要删除的线程的个数,设置为0时直接返回当前线程池线程总数,对线程池不造成任何其他影响
(5)销毁线程池
#include <thread_pool.h>
bool destroy_pool(thread_pool *pool)
返回值:成功返回true,失败返回false
参数:pool --- 要销毁的线程池