工艺品东莞网站建设,韩国网站购物,wordpress网站主机名,个人设计师网站目录标题 什么是同步生产者和消费者模型三者之间的关系消费者生产者模型改进生产者消费者模型特点条件变量的作用条件变量有关的函数条件变量的理解条件变量的使用 什么是同步
这里通过一个例子来带着大家了解一下什么是同步#xff0c;在生活中大家肯定遇到过排队的情景比如… 目录标题 什么是同步生产者和消费者模型三者之间的关系消费者生产者模型改进生产者消费者模型特点条件变量的作用条件变量有关的函数条件变量的理解条件变量的使用 什么是同步
这里通过一个例子来带着大家了解一下什么是同步在生活中大家肯定遇到过排队的情景比如说某个小吃店在做活动然后很多人都在排队然后小王恰巧路过这个小吃店小王知道这个小吃店特别的火里面的东西买的很贵但是非常好吃碰巧今天正在做活动并且优惠的力度特别大于是小王就开始来到队队伍的最后排起队来准备买点小吃回家小王边跟微信好友聊天炫耀自己抢到优惠的同时排在小王后面的人越来越多并且小王也越来越靠近售卖的地方过了一会终于轮到了小王小王买了一份小吃然后付完钱准备离开的时候小王突然想起来自己的妈妈也很喜欢这个小吃然后小王看到队伍后面有好多的人不想重修排队于是小王又抢在下一个人上前购买之前来到收银台再买一份等小王又付完钱刚离开的时候小王看到消息发现一号好兄弟让他帮忙带一份小吃于是他就又抢走下一个人来到收银台之前跑到收银台旁边进行挑选等小王付完钱刚准备离开的时候他又发现自己的二号好兄弟也找他代购小吃于是他又赶在下一个来到收银台之前跑到收银台进行挑选购买就这样反反复复然后就会发现一个现象这条有很多人的队伍停止运转了因为小王是离收银台最近的人所以小王每次都能赶在下一个人之前来到收银台进行下一次购买这就导致了只有小王一个人能买到小吃抢到资源这是不公平的也不是小吃店所期望小吃店之所以做这么大的活动是希望通过这个活动让更多的人都能品尝到这份美味而不是只让小王一个人买到这份美味那么这就是生活中的例子在程序的代码中这样的现象依然存在比如说下面的代码
#includeiostream
#includestring
#includeunistd.h
#includepthread.h
using namespace std;
int ticket_num1000;
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;
void * func(void* args)
{string namestatic_castconst char*(args);int sum0;while(true){pthread_mutex_lock(mutex);// LockGuard lockguard(mutex);if(ticket_num0){usleep(124);coutname 正在进行抢票 ticket_numendl;--ticket_num;sum;pthread_mutex_unlock(mutex);}else{pthread_mutex_unlock(mutex);break;}}coutname抢到票的个数sumendl;return nullptr;
}
int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(tid1,nullptr,func,(void*)user1);pthread_create(tid2,nullptr,func,(void*)user2);pthread_create(tid3,nullptr,func,(void*)user3);pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
}这是一个抢票的程序一共有1000张票我们创建了3个线程互斥的欠票如果资源的分配是公平的话我们应该可以看到每个线程抢到票的个数应该是相差不大的那么将程序运行一下就可以看到下面这样的结果 我们发现user3抢到了绝大部分的票user1只抢到了182张票而user3却1张票都没有抢到那么这就说明上面锁资源的分配是不公平的某些线程可以容易的申请锁资源而有些线程却很难申请到锁的资源申请到了锁资源就要执行一些任务完成事情如果某个线程一直申请到了锁的资源那么这个线程就一直处于工作状态而一直没有申请到锁资源的程序就一直处于空闲状态也就是所谓的忙的忙死闲的闲死那为什么会出现这样的现象呢原因很简单锁只规定了互斥访问也就是让执行流串行的访问某个区域他并没有规定让谁先优先获取锁所以哪个线程能获取到锁完全由线程之间的竞争决定的出现了这样的现象是不符合预期的我们希望每个线程都能够均匀的分配到工作所以这里就采用一个方法当我们排队购买东西的时一旦付完了钱要是想再次购买的话只能重新排队来到队伍的最后不能直接插到最前面那么这里也是同样的道理我们让线程能够按照某种特定的顺序访问临界资源也就是按照某种顺序申请锁资源比如说先是A线程申请锁再是B线程申请锁然后是c线程申请锁等绕了一圈之后才又是A线程申请锁
这样就避免了饥饿问题每个线程都能分配到数量相当的任务我们把这样的解决方法叫做同步。
生产者和消费者模型
那么要想更好的了解线程的同步我们就得了解一下什么是生产者消费者模型在现实中学生就是典型的消费者学生一般会去商场消费买东西那么生产者就是提供商品的工厂学生不会去工厂里面买东西而是去超市里面买东西因为工厂不会卖东西给消费者因为机器的启动成本比较高学生购买的量比较少并且工厂离学生等消费群体比较远不会建立在市区所以消费者会去超市里面买东西超市就是一个平台他建立在市区离学生群体较近面向消费者提供商量货物售卖的服务面向工厂提供大量购买的请求 超市里面不同的商品就有这不同的供货商并且超市面向的消费者比较多所以超市每次向供货商进货的量就比较大所以超市的作用就是集中需求分发产品所以超市扮演的角色就是交易场所生产者并不是无时无刻在生产可能在修理设备可能在停工也可能在生产产品并且消费者也不是无时无刻在消费他们可能在工作可能在上学也可能在干任何事情所以消费者和生产者之间是没有什么关系的他们两做的事情是不会发生干扰的我们把这样的现象称为生产的过程和消费的过程解藕那这里就有两个问题消费者能够一直消费吗消费者不能一直消费当把超市里面的东西买完了也就没得买了想消费也消费不了了所以消费者不能够一直消费那生产者能够一直生产吗肯定也是不行的商场都货架都摆满了你还生产干嘛咧对吧卖不出去就不生产了对吧所以生产者也不能一直生产超市作为临时保存产品的场所他可以保存一定量的产品所以他就可以保证生产者要生产的时候可以一定程度的生产生产者不想生产的时候我这里也有货可以一定程度满足消费者的需求而消费想消费的时候超市目前的存货量也能几乎满足他的需求所以正式超市这样的角色存在就保证了生产者和消费者在一定程度上的解耦而在计算机中我们就把超市这样的角色称为缓冲区。这里为了让大家更好的了解什么是解耦我们可以举一个强耦合的例子函数调用就是强耦合我们以形参的形式将数据交给函数func函数体内对新参进行加工将计算的结果以返回值的方式进行返回所以可以将函数的调用方看成生产者生产了数据把形成的变量认为临时的保存了数据而目标函数就是消费者他拿着临时数据开始了加工和消费我们知道在main函数里面调用了func函数那在执行func函数的时候main函数在干嘛呢答案是什么都没有干就在等func执行结束只有func执行结束main函数才能接着执行所以这就是一个强耦合关系这就好比去小孩放学回家在路上一定会买些吃边吃边回家有些小孩会买辣条有些小孩会买炸火腿肠买辣条的小孩买完就走了而买炸火腿肠的小孩还得等等火腿肠由生的变成熟的才能走所以吃辣条的小孩会早些到家而买火腿肠的则会晚点到家那么计算机中也是这样的解耦一定会效率会高点而耦合则会效率低点所以这就是耦合的例子。
三者之间的关系
因为生产者和消费者都不止是一个线程那么接下来我们就要讨论消费者和消费者之间的关系生产者和生产者之间的关系以及生产者和消费者之间的关系。生产者要将生产的东西放到超市里面消费者要从超市里面拿东西所以超市就是一个公共资源要被生产者和消费者访问那么这里就存在一个问题当超市的货架是空的生产者正要往货架上放东西而消费者这个时候正想拿东西的时候消费者能获取物品成功吗答案是不确定的理想状态下工作人员放东西要么就是放要么就是不放没有中间状态的如果有中间状态他正在放东西的时候他有没有放就决定着我能不能获取东西成功而他有没有放我们是不能确定的所以这里可能就会出现同时访问的问题然后就照成了数据不一致的问题这就好比供应商往超市的货架上放物品时可能存在很多步比如说当前放了多少物品这些物品编码是多少生产日期是多少等等要记录很多步骤而这个时候消费者拿取物品就可能会导致信息的错乱出现问题因为超市里的某些资源是共享的所以就会出现资源的并发访问的问题所以超市就得首先被保护起来生产者在往超市里面存放东西的时候消费者不能从超市里面拿东西而消费者在从超市里面拿东西的时候生产者就不能往里面放东西那么这是生活中的例子我们还可以通过计算机中的例子再进行了解比如说消费者线程想要从缓冲区里面读取数据hello world但是消费者刚读到hello的时候生产者可能把之前的hello world改成hello history那这个时候就会消费者线程读取的数据就会出现问题所以生产者和消费者之间的关系为互斥关系那生产者和生产者之间是什么关系呢答案是竞争关系比如说一个货架上面不能存放两个牌子的物品在超市里面一个货架上要么是康师傅的方便要么是统一的防变量两家的绝对不能放到一起所以在计算机当中生产者和生产者之间是互斥关系消费者和消费者之间也是竞争关系比如说商品只剩下一份了但是两个消费者都想要这个商品那么这个时候消费者之间就是竞争关系也就是所谓的互斥。那么看到了这里我们就可以稍微的总结一下
生产者和生产者之间的关系是互斥的消费者和下消费者之间的关系是互斥的消费者和生产者之间的关系是互斥的
消费者生产者模型改进
最近华为新出的mate60非常的火爆那么我们这里就用遥遥领先来举一个例子消费者小雷想去商店买一台遥遥领先用用小雷的家离商店有点元素于是小雷花了好多时间来到商店可是一问服务员发现遥遥领先卖完了没货了于是小雷只能失望的回家第二天小雷又花了很多时间跑到商店询问瑶瑶领先有没有货可是这个手机实在是太火爆了导致现在依然是缺货的状态所以小王又只能这么无功而返就这样第三天第四天第五天小雷天天来找服务员购买mate60但是服务员只能一次又一次的告诉小雷这个手机缺货了请明天再来看看所以这样的行为 既浪费了消费者的时间也浪费了超市服务员的经历同样的道理生产者生产商品的时候也会不停的询问超市是否需要该物品如果一段时间不需要的话也势必会导致生产者不停的询问这样既浪费了生产者的精力也浪费了商店服务员的精力那么为了解决上述的问题商店的服务员可以和消费者生产者之间使用微信联系当消费者想知道物品是否有货的时候就可以使用微信进行联系不需要亲自来到商店生产者想知道是否需要补货的时候也可以使用微信进行联系而不需要亲自来到商店所以这样就保证了生产者生产往商店里放了一部分消费者就从商店里面拿走一部分这样就让生产者和消费者之间协同了起来不仅提供了生产者的效率还提供了消费者的效率生产者生产数据的时候会进行枷锁和解锁缓冲区当缓冲区满了之后生产者又会枷锁和解锁的访问缓冲区能否装的下资源如果生产者不停的循环枷锁解锁这就又会导致消费者的饥饿问题反过来也是同样的道理所以生产者和消费者之间的关系不仅存在着互斥的关系还存在着同步的关系所以这里总结一下就是321原则
3表示 3种关系消费者和消费者的关系互斥 生产者和生产者的关系互斥消费者和生产者的关系互斥保证共享资源的安全性同步保证生产者和消费的工作效率不然其中的其中一方处于饥饿状态不让其中一方干太多的无用功询问是否有资源或者询问是否还能装的下资源。
2表示 2种角色也就是生产者线程和消费者线程。
1表示 1个交易场所也就是一段特定结构的缓冲区。
那么只要我们想实现生产消费模型本质上就是要维护好上面的321原则。
生产者消费者模型特点
生产线程和消费线程进行解耦。解决生产和消费一段时间的忙闲不均的问题比如说过年的时候工厂的工人都回家所以工厂的生产效率就非常的低但是人们又要买很多的东西回家拜年那么这个时候工厂的生产效率和消费者的消费效率就是不对等的如果没有商店就会导致很多的人想买却买不着商品的问题但是因为超市的存在他可以预先的存储一部分的商品在工厂的生产效率特别低的情况的下也能满足消费者特别高的需求所以这就解决了生产和消费的忙线不均的问题。提高效率比如说超市的存在提供了消费者和生产者的效率如果没有超市消费者就只能自己前往遥远的工厂进行购买而生产者也不敢大量的生产的物品怕消费者嫌弃太远不敢来了那么在程序里面也是同样的道理我们知道main函数在调用其他函数的时候是无法继续执行的而函数可能会因为用户输入数据较慢的原因导致main线程一直处于等待状态但是有了生产者和消费者模型就可以把main线程中用户输入数据的过程看成生产者调用func函数执行func函数打印数据当成消费者这样用户就可以往缓冲区中输入大量的数据即使某一刻不输入数据也不会导致func执行的情况因为缓冲区中还存在数据可以继续供func函数执行这样就提高了程序的运行效率。那么这就是生产者消费者模型的特点。但是这个高效并不是绝对的比如说消费想要获取数据但是超市里面什么数据都没有这时就没有办法获取同理生产者在生产数据的时候发现消费者正在拿数据这时即使将数据生产出来了也没有办法将数据放进去所以在一些场景下该模型就会演化成生产者生产一个消费者消费一个那这个高效究竟高效在哪里呢
条件变量的作用
在上面的学习中我们知道当超市里面的资源满了或者空了的时候消费者和生产者可能就会不停的枷锁访问是否需要资源或者是否有资源然后再解锁虽然这样保证了超市这个公共数据的安全但是这样的现象不仅浪费了消费者的资源还浪费了生产者的资源所以我们就让消费者和生产者之间为同步关系那么为了实现这个同步的关系我们就得使用条件变量当一个线程互斥地访问某个变量时它可能发现在其它线程改变状态之前它什么也做不了。例如一个线程访问队列时发现队列为空它只能等待直到其它线程将一个节点添加到队列中时他才能获取到该节点那么这种情况就需要用到条件变量。条件变量的作用就是当发现公共数据中没有数据时就让该线程挂起等待等公共资源中有数据的时候再将等待的线程唤醒即可。
条件变量有关的函数
条件变量也是一个数据类型使用条件变量之前得定义一个pthread_cond_t类型的对象定义了该变量之后就可以使用pthread_cond_init来进行初始化与锁相似如果条件变量是全局的话也可以使用PTHREAD_COND_INITIALIZER来初始化该函数的声明如下 第一个参数就要传递一个条件变量的指针第二个参数表示条件变量的属性这里不管直接传递null就可以既然有条件变量的初始化那么就有条件变量的销毁销毁就要用pthread_cond_destroy函数想要销毁哪个变量就只需要传递对应变量的地址即可。生产者在访问资源的时候都是先枷锁然后判断生产的条件是否满足最后解锁这就会导致其他线程的饥饿问题但是有了条件变量之后就可以做到条件不满足的时候就不再申请锁资源了而是将自己挂起那么这里的挂起用到的就是pthread_cond_wait函数该函数的声明如下该函数的参数我们后面再聊
既然有函数能在资源条件不满足的时候将线程挂起那么当资源条件满足的时候一定存在一个函数可以将之前等待的线程唤醒那么这个函数就是pthread_cond_signal。
条件变量的理解
1.一个例子 公司招人的时候是需要面试的并且每个岗位都会有大量的人来进行应聘所以面试官所在的面试间外面就围着大量的人每当面试完一个人的时候就让离房间门最近的一个人进来面试但是这样的做法导致了面试的环境非常的乱很多求职人员会因为谁靠的最近而吵来吵去争来争去这既影响了面试时的环境也影响了面试的效率
所以该公司就在离面试房间较远处插上了一个牌子然后规定所有前来面试的人不要在面试房间周围等待而是在这个牌子的左边按照先来后到的顺序排成一排进行等待 这样每面试完一个人就可以直接让牌左边的第一个人前往面试房间即可不需要进行挑选了如果这个时候有人前来准备面试只用排到队伍的最左边即可那么我们就把这里的牌子就称之为条件变量所有应聘者等待面试的时候只能去条件牌子下面等待而面试官叫人前来面试的时候也只会从牌子里面叫应聘者那么这是现实生活在操作系统当中如果共享资源的条件不满足的话线程就只能去定义好的条件变量的上面进行等待当共享资源的条件满足时便会去条件变量上面唤醒线程。
2.一张图 条件变量就是一个结构体变量里面有个status表示当前条件变量的状态还有一个task_struct指针用来维护的队列 操作系统中存在多个线程这些线程在操作系统内部都是一个个的task_struct所以当一个线程申请到了锁但是发现当前的资源不就绪的时候就可以将当前进程的task_struct链接到struct_cond中的task_struct队列里面 等未来我们发现共享资源的条件满足的时候就可以调用pthread_cond_signal函数该函数就是将线程的pcb从队列中取出来放到cpu中执行那么这就是条件变量的基本结构。
条件变量的使用
那么这里我们就通过修改文章一开始的例子来带着大家了解一下条件变量有关函数的使用首先原来代码的大致框架是这样
#includeiostream
#includestring
#includeunistd.h
#includepthread.h
using namespace std;
int ticket_num1002;
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;
void * func(void* args)
{
}
int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(tid1,nullptr,func,(void*)user1);pthread_create(tid2,nullptr,func,(void*)user2);pthread_create(tid3,nullptr,func,(void*)user3);pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
}我们就可以创建一个全局的条件变量
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condPTHREAD_COND_INITIALIZER;然后在func函数里面我们依然是先转换获取线程的名字然后创建一个变量表示当前线程获得的票数总数然后我们创建while循环表示不断的进行抢票while循环的开始我们就先申请锁一旦锁申请成功了我们就直接将该线程放到条件变量里面挂起pthread_wait函数的声明如下 第一个参数表示在哪个条件变量下面等待第二个参数表示当前线程所持有的锁所以第二个参数我们传递锁的地址至于为什么要传递锁的地址我们后面再聊那么这里的代码如下
void * func(void* args)
{string namestatic_castconst char*(args);int sum0;while(true){pthread_mutex_lock(mutex);pthread_cond_wait(cond,mutex);}
}然后我们就判断一下如果当前的票数大于0就对ticket减减对sum的值并打印一句话否则就直接使用break跳出循环不管是哪种情况都得将锁的资源释放循环结束后就打印一下当前线程抢到票数的总和
void * func(void* args)
{string namestatic_castconst char*(args);int sum0;while(true){pthread_mutex_lock(mutex);pthread_cond_wait(cond,mutex);if(ticket_num0){coutname 正在进行抢票 ticket_numendl;--ticket_num;sum;pthread_mutex_unlock(mutex);}else{pthread_mutex_unlock(mutex);break;}}coutname抢到票的个数sumendl;return nullptr;
}因为我们在线程获取锁的下一步就将其添加到了条件变量里面他们没有办法自己将自己唤醒所以就得在主线程里面调用pthread_cond_signal函数将条件变量中的线程一个一个的唤醒该函数的声明如下 参数表示你要唤醒哪个条件变量的线程那么这里的代码就如下
int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(tid1,nullptr,func,(void*)user1);pthread_create(tid2,nullptr,func,(void*)user2);pthread_create(tid3,nullptr,func,(void*)user3);for(int i0;i10023;i){usleep(1234);pthread_cond_signal(cond);}pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
}因为这里的3个线程在ticket为0的时候也会申请锁然后去条件变量下等待所以这里得多加三次那么这里我们的代码就修改完了程序运行的结果如下 可以看到这里的运行结果符合我们的预期那么这就是信号量的作用和用法在上面的图片中我们还遇到一个叫 pthread_cond_broadcast函数这函数的作用也是唤醒线程pthread_cond_signal函数是一次唤醒一个线程那么这个函数的作用就是一次唤醒所有等待的线程比如说将代码修改成下面这样
for(int i0;i10023;i)
{sleep(1);pthread_cond_broadcast(cond);cout----------------------endl;}运行的结果如下 可以看到一次性唤醒了三个线程那么这就是信号量的使用最后我们来谈谈为什么信号量挂起函数得传递一个锁指针原因很简单你被挂起了其他线程还得申请这个锁对公共资源进行访问所以得传递这个锁指针来释放你之前申请的锁资源供其他线程使用等你被唤醒的时候又会将你之前申请的锁资源还给你那么这就是本篇文章的全部内容。