当前位置: 首页 > news >正文

【Linux操作系统】线程的互斥与同步

目录

  • 一、线程互斥
  • 二、线程同步

一、线程互斥

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

抢票代码:
一共有5000张票,创建4个线程,最后打印出每个线程抢到的票数

int g_ticket = 5000;
class DataThread
{
public:DataThread(int& ticket, const string& name):_ticket(ticket), _name(name), _total(0){}~DataThread(){}string _name;int& _ticket;//全局的int _total;
};
void route(DataThread* td)
{while(true){//抢票if(td->_ticket > 0){usleep(1000);printf("%s is run..., ticket: %d\n", td->_name.c_str(), td->_ticket);td->_ticket--;td->_total++;}else {break;}}
}
const int num = 4;
int main()
{vector<Thread<DataThread*>> threads;vector<DataThread*> datas;for(int i = 0; i < num; i++){string name = "thread-" + to_string(i+1);DataThread* td = new DataThread(g_ticket, name);threads.emplace_back(route, td, name);datas.emplace_back(td);}//创建for(auto& thread:threads){thread.Satrt();}//等待for(auto& thread:threads){thread.Join();}//打印每个线程获取的票数for(auto& data:datas){cout << data->_name << " total is: " << data->_total << endl;}return 0;
}

在这里插入图片描述
为什么出现负数?因为ticket是共享资源,没有被保护,对它的判断不是原子的。在ticket等于1的时候,就可能有多个线程进入判断逻辑,然后都要执行把数据从内存读取到CPU,减法操作,再写回内存。这就导致出现了负数的情况

解决方法:加锁
定义的锁是全局的或静态的:
在这里插入图片描述

定义的锁是局部的:
在这里插入图片描述

加锁:
在这里插入图片描述

申请锁成功,函数就会返回,可以继续运行
申请锁失败,函数就会阻塞,不可以继续运行
函数调用失败,出错返回

解锁:
在这里插入图片描述

防止出现并发访问的问题,要保护全局共享资源,保护共享资源本质是保护临界区,访问临界资源的代码叫临界区。加锁是为了让多线程串行执行,即一个线程成功加上锁,让它访问临界资源,其他线程要等待,直到这个线程访问完才可以让其他线程竞争锁。这个过程叫互斥。

全局加锁代码:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//全局加锁
void route(DataThread* td)
{while(true){pthread_mutex_lock(&mutex);//加锁//抢票if(td->_ticket > 0){usleep(1000);printf("%s is run..., ticket: %d\n", td->_name.c_str(), td->_ticket);td->_ticket--;pthread_mutex_unlock(&mutex);//解锁td->_total++;}else {pthread_mutex_unlock(&mutex);//解锁break;}}
}

在这里插入图片描述

局部加锁代码:

class DataThread
{
public:DataThread(int& ticket, const string& name, pthread_mutex_t& mutex):_ticket(ticket), _name(name), _total(0), _mutex(mutex){}~DataThread(){}string _name;int& _ticket;//全局的int _total;pthread_mutex_t& _mutex;//共用一把锁
};void route(DataThread* td)
{while(true){pthread_mutex_lock(&td->_mutex);//加锁//抢票if(td->_ticket > 0){usleep(1000);printf("%s is run..., ticket: %d\n", td->_name.c_str(), td->_ticket);td->_ticket--;pthread_mutex_unlock(&td->_mutex);//解锁td->_total++;}else {pthread_mutex_unlock(&td->_mutex);//解锁break;}}
}
const int num = 4;
int main()
{pthread_mutex_t mutex;pthread_mutex_init(&mutex, nullptr);//初始化锁vector<Thread<DataThread*>> threads;vector<DataThread*> datas;for(int i = 0; i < num; i++){string name = "thread-" + to_string(i+1);DataThread* td = new DataThread(g_ticket, name, mutex);threads.emplace_back(route, td, name);datas.emplace_back(td);}//创建for(auto& thread:threads){thread.Satrt();}//等待for(auto& thread:threads){thread.Join();}//打印每个线程获取的票数for(auto& data:datas){cout << data->_name << " total is: " << data->_total << endl;}pthread_mutex_destroy(&mutex);//释放锁return 0;
}

在这里插入图片描述

优化代码,对线程和锁进行封装——RAII风格

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__
#include <iostream>
#include <pthread.h>
using namespace std;class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex);//构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);//析构解锁}
private:pthread_mutex_t *_mutex;
};#endifvoid route(DataThread* td)
{while(true){LockGuard guard(&td->_mutex);//临时对象,构造即加锁,析构即解锁//抢票if(td->_ticket > 0){usleep(1000);printf("%s is run..., ticket: %d\n", td->_name.c_str(), td->_ticket);td->_ticket--;td->_total++;}else {break;}}
}

在这里插入图片描述

互斥的底层原理
在这里插入图片描述
内存中有一个数据为1,CPU要处理线程1,首先,线程1会把0带入到寄存器中,然后寄存器中的数据与内存中的数据作交换,这样线程1得到了数据为1,这个过程线程1完成了加锁。如果要解锁,就换回去。又来了个线程2,此时线程1切换为线程2(注意:CPU执行线程1时是可以随时被切换的),线程1要带走寄存器中的1,然后线程2把0带入到寄存器中,再与内存的数据作交换,还是0,说明锁被其他线程占用,线程2就会挂起等待。再到线程1,1放会寄存器中,然后CPU处理线程1,加锁的只有线程1,所以线程1可以访问临界资源,即互斥。

CPU寄存器硬件只有一套,但是CPU寄存器内部的数据,可以有多套;
数据在内存里,所有线程都可以访问,是共享的,但是转移到CPU寄存器中就是一个线程私有的;
交换的本质:不是拷贝到寄存器,而是所有的线程竞争一把锁

正在访问临界区资源的线程可以被OS调度吗?

不能。一个线程正在访问临界区,还没有释放锁,对于其他线程来说,要竞争到锁,要么这个锁已经被释放,要么这个锁没有被前面那个线程申请成功。总结:加锁的线程访问临界区,不能被OS调度切换,此时(加锁的线程)访问临界区是原子的(是线程安全的)。

二、线程同步

概念:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

条件变量:
是什么?
栗子:一个人放苹果到盘子,但是不许说话;另一个人从盘子上拿苹果,但是蒙上眼睛(看不见);一个负责放,另一个负责拿,不能同时进行,是互斥的。放苹果的要申请到锁,才能放苹果,拿苹果的也要申请到锁,才能去盘子上拿苹果(可能盘子上没有苹果)。总之就是两个人要竞争一把锁,如果拿苹果的人竞争性更强,每次都是他申请到锁,放苹果的人就没机会放苹果了,这也导致拿苹果的人每次申请到锁后去盘子上总是没有苹果拿,但是他又一直占用锁(每次申请到锁的都是他),所以放苹果的不能申请到锁放苹果,拿苹果的在盘子上也没有苹果拿,这就导致整个过程很不合理。
在这里插入图片描述
解决方式:放苹果的人有一个铃铛,他申请到锁,并往盘子上放了苹果,即确定盘子上有苹果时,他就会敲响一下铃铛,提醒拿苹果的人可以来申请锁并且去拿苹果了。如果没有铃铛没有响,拿苹果的人就不要一直去申请锁,这样放苹果的人就有机会去申请锁然后放苹果。

拿苹果的人不止有一个,可以有多个,组成一个队列。铃铛响多次,表示让多个人来申请锁,竞争到锁的去拿苹果,然后返回队列,没竞争到锁的继续等待。条件变量等价于这个铃铛和多个线程组成的队列(保证顺序性)。

认识接口:
全局条件变量:
在这里插入图片描述

初始化:

int pthread_cond_init(pthread_cond_t *restrict cond,constpthread_condattr_t *restrict attr);

销毁:

int pthread_cond_destroy(pthread_cond_t *cond)

等待:

int pthread_cond_wait(pthread_cond_t *restrictcond,pthread_mutex_t *restrict mutex);

唤醒一个线程:

int pthread_cond_signal(pthread_cond_t *cond);

唤醒所有等待的线程:

int pthread_cond_broadcast(pthread_cond_t *cond);

验证线程同步代码:
一个Master控制其他线程,条件变量+锁,然后将等待的线程唤醒

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>using namespace std;pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;//条件变量
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;//互斥锁
void *MasterCore(void *args)
{string name = static_cast<char *>(args);while (true){sleep(3);cout << "Master开始工作.."<<endl;//将线程一个一个唤醒pthread_cond_signal(&gcond);cout<<"唤醒一个线程:"<<endl;sleep(1);}return nullptr;
}
void *SlaverCore(void *args)
{string name = static_cast<char *>(args);while (true){//加锁pthread_mutex_lock(&gmutex);//设置条件变量,让线程在等待队列pthread_cond_wait(&gcond, &gmutex);cout << "线程在等待:" << name<<endl;//解锁pthread_mutex_unlock(&gmutex);}return nullptr;
}void StartSlaver(vector<pthread_t> *tidptr, int num)
{for (int i = 0; i < num; i++){pthread_t tid;char *name = new char[64];snprintf(name, 64, "thread-%d", i + 1);int n = pthread_create(&tid, nullptr, SlaverCore, name);if (n == 0){cout << "Slaver Thread create success" << endl;}tidptr->emplace_back(tid);}
}
void StartMaster(vector<pthread_t> *tidptr)
{pthread_t tid;int n = pthread_create(&tid, nullptr, MasterCore, (void *)"MasterThread");if (n == 0){cout << "Master Thread create success" << endl;}tidptr->emplace_back(tid);
}
void WaitThread(vector<pthread_t> tidptr)
{for(auto& tid:tidptr){pthread_join(tid, nullptr);}
}
int main()
{vector<pthread_t> tids;StartMaster(&tids);StartSlaver(&tids, 5);WaitThread(tids);return 0;
}

在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • machine learning - 2
  • 【VUE】Vue 组件详解
  • 【SpringBoot】使用Redis
  • 一文搞懂 | Pytorch维度转换操作:view,reshape,permute,flatten函数详解
  • linux中vim常用命令大全
  • 微软RD客户端 手机 平板 远程控制 Windows桌面
  • 【Linux】进程优先级|进程切换
  • 【机器学习-神经网络】循环神经网络
  • GateWay三大案例组件
  • 后端开发刷题 | 最长公共子序列(非连续)
  • 科研绘图系列:R语言PCoA图(PCoA plot)
  • 店匠科技携手Stripe共谋电商支付新篇章
  • 前端知识HTMLCSS
  • 从供货上游到下游消费者平台搭建 多商家供货供应链商城开发关键点
  • RabbitMQ 02 操作,配置信息,用户权限
  • python3.6+scrapy+mysql 爬虫实战
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • 【RocksDB】TransactionDB源码分析
  • Hexo+码云+git快速搭建免费的静态Blog
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JavaScript函数式编程(一)
  • js如何打印object对象
  • Laravel5.4 Queues队列学习
  • Promise面试题,控制异步流程
  • Redis 中的布隆过滤器
  • swift基础之_对象 实例方法 对象方法。
  • TypeScript迭代器
  • 百度小程序遇到的问题
  • 产品三维模型在线预览
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 订阅Forge Viewer所有的事件
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 聊聊directory traversal attack
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 设计模式 开闭原则
  • 详解NodeJs流之一
  • 学习JavaScript数据结构与算法 — 树
  • 云大使推广中的常见热门问题
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 扩展资源服务器解决oauth2 性能瓶颈
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (8)STL算法之替换
  • (rabbitmq的高级特性)消息可靠性
  • (二)原生js案例之数码时钟计时
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (力扣题库)跳跃游戏II(c++)
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (删)Java线程同步实现一:synchronzied和wait()/notify()
  • (四)stm32之通信协议
  • (新)网络工程师考点串讲与真题详解
  • (转)LINQ之路
  • (转载)Linux网络编程入门