线程间竞争
一、基本概念
原子操作:中途不会被打断的操作称为原子操作(不会被其他线程竞争影响的操作)
竞争与同步:
同一个进程中的线程共享进程中绝大多数资源,当它们随意竞争时可能导致资源被破坏、脏数据、不完整、不一致的情况
通过一些方法让线程在竞争资源时相互协调,避免出现以上情况,这种线程间协同工作称为线程同步
临界区和临界资源:
被多个线程同时访问的代码称为临界区、被同时访问的资源称为临界资源
二、互斥量(互斥锁)
有些系统的man手册没有关于mutex的文档需要安装:
sudo apt-get install manpages-posix-dev
pthread_mutex_t 是一种数据类型 可以定义互斥量变量
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
功能:初始化一个互斥量
mutex:要初始化的互斥量变量
attr:对互斥量的属性进行设置,一般给NULL即可
注意:一般默认是开锁状态,也可使用PTHREAD_MUTEX_INITIALIZER 对互斥量变量进行初始化
例如:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:对互斥量进行加锁,成功则继续执行下文,失败则阻塞,直到互斥量被解锁并加锁成功,才返回
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:对互斥量尝试加锁,成功(0)或者失败(EBUSY)都立即返回
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:对互斥量解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁互斥量
三、信号量
与XSI中的信号量原理相同,相当于线程之间使用的同一个计数器,用于统计、控制访问有限的共享资源的线程数量
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
sem:被初始化的信号量
pshared:
0 只能在本进程内使用
非0 表示该信号量可以以共享内存的形式,让多个进程共享使用(Linux不支持)
value:信号量的初始值
int sem_wait(sem_t *sem);
功能:对信号量-1,如果信号量为0不够减,则阻塞,减成功则继续执行
int sem_trywait(sem_t *sem);
功能:对信号量尝试-1,成功(0)或失败(EAGAIN)都立即返回
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
功能:对信号量-1,如果不够减则等待abs_timeout时间,如果超时返回ETIMEDOUT错误编码
int sem_post(sem_t *sem);
功能:对信号量+1
int sem_destroy(sem_t *sem);
功能:销毁信号量
四、死锁
1、什么是死锁
多个进程或者线程之间互相等待对方手中的资源,在得到新的资源之前不会主动释放自己手中的资源,这样如果形成了等待环路,就称之为死锁现象
2、产生死锁的四大必要条件
资源互斥:资源只有两种状态,只有可用和不可用状态,不能同时使用,同一时间内只能被一个进程或线程使用
占有且请求:已经得到资源的进程或线程,对旧资源保持占有并继续请求新的资源
资源不可剥夺:资源已经分配给进程或线程后,不能被其他进程或线程强制性获取、除非资源的占有者主动释放
环路等待:当死锁发生时,系统中必定有两个或两个以上的进程或线程执行路线形成一条等待的环路
注意:以上四个条件同时成立,就会形成死锁,死锁一旦产生基本无解,以现在的操作系统是无法解决死锁,因此只能防止死锁的产生
3、如何防止死锁的产生
破坏资源互斥:
想办法让资源能够共享使用
缺点:受现实环境和资金的影响无法让资源共享
破坏占有且请求:
采用预分配的方式,让进程或线程在运行前一次性申请所有资源,如果在资源没有满足时不投入运行
缺点:系统资源的占用会严重浪费,因为有些资源可能开始时使用,但是有些资源可能最后才使用
破坏资源不可剥夺:
当一个进程或线程已经占用一个不可被剥夺的资源时,并且在请求新资源无法被满足时,则释放已经占用的资源,等待一段时间后重新请求
缺点:该策略实现比较麻烦,而且释放已经申请的资源可能会导致前一阶段的工作无效浪费,反复地申请释放资源也会增加系统开销、占用CPU和计算器、内存等资源
破坏环路等待:
给每个资源起编号,进程或线程按照编号顺序依次请求资源,并且只有拿到前一个资源才能继续请求下一个资源
缺点:资源的编号相对稳定,当资源增加或删除时受到很大影响
4、如何判断死锁
1、画出资源分配图
2、简化资源分配图
3、使用死锁判断原理:如果没有环路一定不会出现死锁
了解:银行家算法
五、条件变量
当某些条件满足时,可让线程自己进入睡眠,也可以当某些条件满足时唤醒正在睡眠的线程
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
功能:初始化条件变量
cond:要初始化的条件变量
attr:默认给NULL即可
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
注意:也可以使用PTHREAD_COND_INITIALIZER赋值方式初始化
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:让当前线程睡入cond,并解锁mutex
返回值:直到线程不被唤醒才返回
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒cond中正在睡眠的一个线程,在唤醒前要确保锁处于打开状态,当线程醒来时该线程会自动把锁重新加上
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒cond中所有线程,线程是否醒来却决于能否再次加锁
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
功能:让当前线程睡入cond,并解锁mutex,只睡abstime时间,超时会被操作系统唤醒
int pthread_cond_destroy(pthread_cond_t *cond);
功能;销毁条件变量
注意:使用条件变量可以实现生产者与消费者模型
六、生产者与消费者模型(线程池、数据池)
生产者:生产数据的线程
消费者:使用数据的线程
仓库:临时存放数据的缓冲区(仓库解决了生产、消费不匹配)
可能产生的问题:
消费快于生产:仓库空虚、饿死
生产快于消费:仓库爆满、撑死
使用条件变量来解决以上问题:
当缓冲区空的时候,消费者线程睡入条件变量(empty),通知生产者线程全部醒来(full)
当缓冲区满的时候,生产者线程睡入条件变量(full),通知消费者线程全部醒来(empty)
七、哲学家进餐问题
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
进程、线程相关的问题:
1、进程与线程的区别
2、进程处理多任务时需要解决什么问题
3、线程处理多任务时需要解决什么问题
4、TCP服务端的编程模型有哪几种?以及它们的优缺点?
5、随着客户端的连接和退出越来越频繁,服务端都需要频繁地创建、销毁线程,该过程会比较耗时,如何解决?
Windows编程准备工作:
1、解压编译器mingw到C盘
2、复制:C:\mingw\bin 路径
3、右击此电脑->属性->高级系统设置->高级->环境变量->Path->编辑->新建->粘贴路径->确定
4、win+r->输入cmd->输入gcc -v 测试编译器是否添加成功(部分电脑需要用管理员权限打开cmd再测试)
5、下载VSCode 安装三个插件
VSCode配置:
1、设置->扩展->Run Code...->Run in Termial 等三项打钩
2、Exectuor Map->编辑->修改 ->保存
"c": "cd $dir && gcc $fileName -std=gnu99 -lws2_32 -lpthread -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
"cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
Windows与Linux的socket网络编程区别:
1、额外加库 -lws2_32
2、头文件不同
3、先初始化网络库 固定的
4、使用closesocket关闭socket描述符