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

linux入门---线程的同步

目录标题

  • 什么是同步
  • 生产者和消费者模型
  • 三者之间的关系
  • 消费者生产者模型改进
  • 生产者消费者模型特点
  • 条件变量的作用
  • 条件变量有关的函数
  • 条件变量的理解
  • 条件变量的使用

什么是同步

这里通过一个例子来带着大家了解一下什么是同步,在生活中大家肯定遇到过排队的情景比如说某个小吃店在做活动然后很多人都在排队,然后小王恰巧路过这个小吃店,小王知道这个小吃店特别的火里面的东西买的很贵但是非常好吃,碰巧今天正在做活动并且优惠的力度特别大,于是小王就开始来到队队伍的最后排起队来准备买点小吃回家,小王边跟微信好友聊天炫耀自己抢到优惠的同时排在小王后面的人越来越多并且小王也越来越靠近售卖的地方,过了一会终于轮到了小王,小王买了一份小吃然后付完钱准备离开的时候小王突然想起来自己的妈妈也很喜欢这个小吃然后小王看到队伍后面有好多的人不想重修排队,于是小王又抢在下一个人上前购买之前来到收银台再买一份,等小王又付完钱刚离开的时候小王看到消息发现一号好兄弟让他帮忙带一份小吃于是他就又抢走下一个人来到收银台之前跑到收银台旁边进行挑选,等小王付完钱刚准备离开的时候他又发现自己的二号好兄弟也找他代购小吃于是他又赶在下一个来到收银台之前跑到收银台进行挑选购买,就这样反反复复然后就会发现一个现象这条有很多人的队伍停止运转了,因为小王是离收银台最近的人,所以小王每次都能赶在下一个人之前来到收银台进行下一次购买,这就导致了只有小王一个人能买到小吃抢到资源,这是不公平的也不是小吃店所期望,小吃店之所以做这么大的活动是希望通过这个活动让更多的人都能品尝到这份美味而不是只让小王一个人买到这份美味,那么这就是生活中的例子,在程序的代码中这样的现象依然存在,比如说下面的代码:

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
using namespace std;
int ticket_num=1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void * func(void* args)
{string name=static_cast<const char*>(args);int sum=0;while(true){pthread_mutex_lock(&mutex);// LockGuard lockguard(&mutex);if(ticket_num>0){usleep(124);cout<<name<<" 正在进行抢票 "<< ticket_num<<endl;--ticket_num;sum++;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}cout<<name<<"抢到票的个数:"<<sum<<endl;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那这个时候就会消费者线程读取的数据就会出现问题,所以生产者和消费者之间的关系为互斥关系,那生产者和生产者之间是什么关系呢?答案是竞争关系比如说一个货架上面不能存放两个牌子的物品,在超市里面一个货架上要么是康师傅的方便要么是统一的防变量两家的绝对不能放到一起,所以在计算机当中生产者和生产者之间是互斥关系,消费者和消费者之间也是竞争关系比如说商品只剩下一份了但是两个消费者都想要这个商品那么这个时候消费者之间就是竞争关系也就是所谓的互斥。那么看到了这里我们就可以稍微的总结一下:

  1. 生产者和生产者之间的关系是互斥的
  2. 消费者和下消费者之间的关系是互斥的
  3. 消费者和生产者之间的关系是互斥的

消费者生产者模型改进

最近华为新出的mate60非常的火爆,那么我们这里就用遥遥领先来举一个例子,消费者小雷想去商店买一台遥遥领先用用,小雷的家离商店有点元素于是小雷花了好多时间来到商店,可是一问服务员发现遥遥领先卖完了没货了于是小雷只能失望的回家,第二天小雷又花了很多时间跑到商店询问瑶瑶领先有没有货,可是这个手机实在是太火爆了导致现在依然是缺货的状态,所以小王又只能这么无功而返,就这样第三天第四天第五天,小雷天天来找服务员购买mate60,但是服务员只能一次又一次的告诉小雷这个手机缺货了请明天再来看看,所以这样的行为
既浪费了消费者的时间也浪费了超市服务员的经历,同样的道理生产者生产商品的时候也会不停的询问超市是否需要该物品,如果一段时间不需要的话也势必会导致生产者不停的询问,这样既浪费了生产者的精力也浪费了商店服务员的精力,那么为了解决上述的问题商店的服务员可以和消费者生产者之间使用微信联系,当消费者想知道物品是否有货的时候就可以使用微信进行联系不需要亲自来到商店,生产者想知道是否需要补货的时候也可以使用微信进行联系而不需要亲自来到商店,所以这样就保证了生产者生产往商店里放了一部分,消费者就从商店里面拿走一部分,这样就让生产者和消费者之间协同了起来不仅提供了生产者的效率还提供了消费者的效率,生产者生产数据的时候会进行枷锁和解锁缓冲区,当缓冲区满了之后生产者又会枷锁和解锁的访问缓冲区能否装的下资源,如果生产者不停的循环枷锁解锁这就又会导致消费者的饥饿问题,反过来也是同样的道理,所以生产者和消费者之间的关系不仅存在着互斥的关系还存在着同步的关系,所以这里总结一下就是321原则:

3表示:
3种关系(消费者和消费者的关系(互斥), 生产者和生产者的关系(互斥),消费者和生产者的关系(互斥(保证共享资源的安全性),同步(保证生产者和消费的工作效率不然其中的其中一方处于饥饿状态不让其中一方干太多的无用功:询问是否有资源或者询问是否还能装的下资源))。

2表示
2种角色也就是生产者线程和消费者线程。

1表示
1个交易场所也就是一段特定结构的缓冲区。

那么只要我们想实现生产消费模型,本质上就是要维护好上面的321原则。

生产者消费者模型特点

  1. 生产线程和消费线程进行解耦。
  2. 解决生产和消费一段时间的忙闲不均的问题,比如说过年的时候工厂的工人都回家所以工厂的生产效率就非常的低,但是人们又要买很多的东西回家拜年,那么这个时候工厂的生产效率和消费者的消费效率就是不对等的,如果没有商店就会导致很多的人想买却买不着商品的问题,但是因为超市的存在他可以预先的存储一部分的商品,在工厂的生产效率特别低的情况的下也能满足消费者特别高的需求,所以这就解决了生产和消费的忙线不均的问题。
  3. 提高效率,比如说超市的存在提供了消费者和生产者的效率,如果没有超市消费者就只能自己前往遥远的工厂进行购买,而生产者也不敢大量的生产的物品怕消费者嫌弃太远不敢来了,那么在程序里面也是同样的道理:我们知道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中执行,那么这就是条件变量的基本结构。

条件变量的使用

那么这里我们就通过修改文章一开始的例子来带着大家了解一下条件变量有关函数的使用,首先原来代码的大致框架是这样:

#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>
using namespace std;
int ticket_num=1002;
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 cond=PTHREAD_COND_INITIALIZER;

然后在func函数里面我们依然是先转换获取线程的名字,然后创建一个变量表示当前线程获得的票数总数,然后我们创建while循环表示不断的进行抢票,while循环的开始我们就先申请锁,一旦锁申请成功了我们就直接将该线程放到条件变量里面挂起,pthread_wait函数的声明如下:
在这里插入图片描述
第一个参数表示在哪个条件变量下面等待,第二个参数表示当前线程所持有的锁所以第二个参数我们传递锁的地址(至于为什么要传递锁的地址我们后面再聊),那么这里的代码如下:

void * func(void* args)
{string name=static_cast<const char*>(args);int sum=0;while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);}
}

然后我们就判断一下如果当前的票数大于0就对ticket减减对sum的值++并打印一句话,否则就直接使用break跳出循环,不管是哪种情况都得将锁的资源释放,循环结束后就打印一下当前线程抢到票数的总和:

void * func(void* args)
{string name=static_cast<const char*>(args);int sum=0;while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);if(ticket_num>0){cout<<name<<" 正在进行抢票 "<< ticket_num<<endl;--ticket_num;sum++;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}cout<<name<<"抢到票的个数:"<<sum<<endl;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 i=0;i<1002+3;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 i=0;i<1002+3;i++)
{sleep(1);pthread_cond_broadcast(&cond);cout<<"----------------------"<<endl;}

运行的结果如下:
在这里插入图片描述
可以看到一次性唤醒了三个线程那么这就是信号量的使用,最后我们来谈谈为什么信号量挂起函数得传递一个锁指针,原因很简单你被挂起了其他线程还得申请这个锁对公共资源进行访问,所以得传递这个锁指针来释放你之前申请的锁资源供其他线程使用,等你被唤醒的时候又会将你之前申请的锁资源还给你,那么这就是本篇文章的全部内容。

相关文章:

linux入门---线程的同步

目录标题 什么是同步生产者和消费者模型三者之间的关系消费者生产者模型改进生产者消费者模型特点条件变量的作用条件变量有关的函数条件变量的理解条件变量的使用 什么是同步 这里通过一个例子来带着大家了解一下什么是同步&#xff0c;在生活中大家肯定遇到过排队的情景比如…...

UI设计一定不能错过的4款常用工具

虽然设计审美很重要&#xff0c;但软件只是一种工具&#xff0c;但就像走楼梯和坐电梯到达顶层一样&#xff0c;电梯的效率显然更高&#xff0c;易于使用的设计工具也是如此。让我们了解一下UI设计的主流软件&#xff0c;以及如何选择合适的设计软件。 即时设计 软件介绍 即…...

JavaScript 基础 - 第2天

理解什么是流程控制&#xff0c;知道条件控制的种类并掌握其对应的语法规则&#xff0c;具备利用循环编写简易ATM取款机程序能力 运算符语句综合案例 运算符 算术运算符 数字是用来计算的&#xff0c;比如&#xff1a;乘法 * 、除法 / 、加法 、减法 - 等等&#xff0c;所以经…...

MyBatis和JDBC异同点

MyBatis和JDBC是两种不同的数据库访问技术&#xff0c;它们有以下几点区别&#xff1a; 抽象层级&#xff1a;JDBC&#xff08;Java Database Connectivity&#xff09;是Java提供的一种标准API&#xff0c;用于与数据库进行交互。它提供了一系列的接口和类&#xff0c;使开发人…...

关于yarn安装一些东西报错时的处理方法

The engine "node" is incompatible with this module. Expected version "^14.18.0 || ^16.14.0 || >18.0.0". Got "17.9.0"出现遮掩刮得错误时直接使用这个命令 yarn config set ignore-engines true 忽略错误就可以了 直接安装自己想安装…...

datagrip 使用自定义参数

两种使用方法&#xff1a; 1. 直接打问号❓&#xff0c;执行的时候会问你这个问号填什么 2. 设置参数&#xff0c;执行的时候会问你这个参数填什么 格式&#xff1a;${xxxx} xxxx是你定义的参数名字 SELECT ${column_name} FROM actor WHERE actor_id${actor_id} 执行就会问…...

css实现平行四边形按钮

当使用CSS实现平行四边形按钮时&#xff0c;可以使用transform属性和::before或::after伪元素来创建。下面是一个示例代码&#xff1a; <!DOCTYPE html> <html> <head> <style> .button {width: 150px;height: 50px;background-color: #4CAF50;color: …...

Jmeter只能做性能测试吗?

Jmeter除了可以性能测试&#xff0c;还能做接口测试 1、Jmeter和Fiddler&#xff0c;Postman有什么区别? Fiddler&#xff1a;虽然有接口测试功能&#xff0c;很少用来做接口测试。 一般用Fiddle来做抓包和异常测试&#xff0c;辅助接口测试。Postman&#xff1a; 是接口调试…...

Jmeter调用测试片段 —— 模块控制器

可以使用模块控制器调用测试片段。模块控制器提供了一种在运行时将测试片段替换为当前测试计划的机制。测试片段可以位于任何线程组中。 1、打开一个Jmeter窗口&#xff0c;添加好线程组、用户定义变量、模块控制器、测试片段、察看结果树。 2、用户定义变量同样定义好访问ip及…...

数组类型题目总结

跟着代码随想录的算法训练营进行训练 目前刷的数组 大部分都是需要遍历后 修改、移动、覆盖元素。 在不使用额外辅助空间的情况下&#xff1a; 在需要进行前后元素对比&#xff0c;使用双层for循环遍历的题目&#xff0c;有增删改查的操作时&#xff0c;就可以考虑使用双指针进…...

机器学习2:决策树--基于信息增益的ID3算法

1.决策树的简介 建立决策树的过程可以分为以下几个步骤: 计算每个特征的信息增益或信息增益比,选择最优的特征作为当前节点的划分标准。根据选择的特征将数据集划分为不同的子集。对每个子集递归执行步骤 1 和步骤 2,直到满足终止条件。构建决策树,并输出。基于信息增益的…...

centos7完全卸载和安装mysql8

问题描述 最近安装了MYSQL8&#xff0c;遇到了各种问题&#xff0c;总体汇总一下&#xff0c;凡是无法启动就是my.cnf和初始化的参数不匹配。 第一种 启动前设置了mysqld --initialize --usermysql --lower-case-table-names1&#xff0c;my.cnf文件却没有修改就去启动。 第…...

常用的解析XML的开源库

以下是一些常用的解析XML的开源库: DOM4J:DOM4J是一个基于Java的XML处理库,提供了DOM、SAX和StAX三种解析方式,可以解析和操作XML文档。它提供了丰富的API,可以用于生成、处理和解析XML文档。JAXB:JAXB是一个基于Java的XML处理库,它可以将Java对象映射到XML文档,也可以将XML文…...

SQLITE3 函数接口

简述 sqlite3 接口的核心元素: 两大对象&#xff0c;八大函数&#xff1b; 其中两个对象指的是: sqlite3 数据库连接对象 数据库的连接句柄(数据库的文件描述符) 代表你打开的那个 sqlite3 的数据库文件,后序对数据库的操作都需要用到这个对象 sqlite3_stmt SQL 语句对象…...

RISC-V IDE MRS无感远程协助模块详解

RISC-V IDE MRS无感远程协助模块详解 一、说明 1.1 概述 针对RISC-V/ARM等内核MCU的嵌入式集成开发环境MRS(MounRiver Studio)从V1.90版本开始内置无感远程协助模块&#xff08;Sensorless Remote Assistant Module&#xff0c;以下简称SRA模块&#xff09;。SRA模块是一款支…...

APA技术方案及关键点

APA即我们平时用的最多的基础泊车功能(自动泊车辅助)&#xff0c;按照功能来分&#xff0c;有下列子功能&#xff1a; 1.AVM(全景影像)&#xff0c;四路环视鱼眼动态拼接&#xff0c;去畸变&#xff0c;提供给用户车身周围360的环境图像信息&#xff0c;有2D(单路鱼眼图)&…...

WordPress外链页面安全跳转插件

老白博客我参照csdn和腾讯云的外链跳转页面&#xff0c;写了一个WordPress外链安全跳转插件&#xff1a;给网站所有第三方链接添加nofollow标签和重定向功能&#xff0c;提高网站安全性。插件包括两个样式&#xff0c;由于涉及到的css不太一样&#xff0c;所以分别写了两个版本…...

【牛客网】安全—加密和安全

每日一练 Day1&#xff1a; 1.信息安全的基本属性是&#xff08; D &#xff09; A.保密性 B.完整性 C.可用性&#xff0c;可靠性&#xff0c;可控性 D.A、B、C都是 信息安全的基本属性通常可以归纳为以下几个方面&#xff1a; 保密性&#xff08;Confidentiality&#xf…...

Mybatis基础操作

基础操作 增删改查 Select 是查询类的注解&#xff0c;所有的查询均使用这个Result 修饰返回的结果集&#xff0c;关联实体类属性和数据库字段一一对应&#xff0c;如果实体类属性和数据库属性名保持一致&#xff0c;就不需要这个属性来修饰。Insert 插入数据库使用&#xff…...

Java实验二类编程实验

1.编写一个代表三角形的类&#xff08;Triangle.java&#xff09;。 其中&#xff0c;三条边a,b,c&#xff08;数据类型为double类型&#xff09;为三角形的属性&#xff0c;该类封装有求三角形的面积和周长的方法。分别针对三条边为3、4、5和7、8、9的两个三角形进行测试&…...

css文本溢出省略号多行单行例子详细

在 CSS 中&#xff0c;可以使用 text-overflow: ellipsis; 属性来实现文本溢出时自动省略号的效果。但是该属性只能用于单行文本溢出的情况&#xff0c;对于多行文本溢出的情况&#xff0c;需要使用一定的技巧才能实现相应的效果。下面将分别介绍单行和多行文本溢出时的实现方法…...

android auto

测试面向汽车的 Android 应用 | Android 开发者 | Android Developers (google.cn)...

opengl基础笔记1

1、opengl运行模式及opengl规范 运行模式&#xff1a;核心模式与立即渲染模式&#xff08;弃用&#xff09; 由于OpenGL的大多数实现都是由显卡厂商编写的&#xff0c;当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL&#xf…...

Flutter中的各种刷新小部件

1.FutureBuilder 用于处理异步操作和构建界面的非常有用的小部件。它通常与 Future 对象一起使用&#xff0c;用于在异步操作完成后构建界面。 import package:flutter/material.dart;void main() > runApp(MyApp());class MyApp extends StatelessWidget {overrideWidget…...

DataxWeb安装部署及使用--真香警告

DataxWeb安装部署及使用–真香警告 文章目录 1.Datax简介1.1 Datax是什么&#xff1f;1.2 Datax的架构1.3 设计理念1.4 DataX3.0框架设计1.5 DataX3.0插件体系1.6 DataX3.0核心架构1.6.1 核心模块介绍1.6.2 DataX调度流程 2.DataxWeb简介2.1 DataxWeb是什么&#xff1f;2.2 Dat…...

OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11. 图像的算术运算 图像的本质是一个矩阵&#xff0c;所以可以对它进行一些常见的算术运算&#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外&#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有…...

创建ABAP数据库表和ABAP字典对象-使用已存在的数据元素增加城市字段04

基于内置域增加一个字段 1.在编辑器中&#xff0c;输入字段的名称&#xff0c;后跟冒号:city:。暂时忽略这个错误。2. 输入/MOC/C并使用自动补全(**Ctrl空格**)&#xff0c;输入类型。3. 然后添加一个分号:city: /moc/city;4.在SAP GUI中查看&#xff0c;字段已经新增...

Centos7上安装 Node.js

文章目录 一、前言二、步骤三、涉及nodejs&#xff0c;centos还是少用吧 一、前言 centos7安装nodejs如果直接安装较高版本会包错误&#xff0c;无法运行npm node: /lib64/libm.so.6: version GLIBC_2.27‘ not found (required by node)二、步骤 网上说的下载升级编译器的方…...

栈及其栈的模拟实现和使用

1. 栈(Stack) 1.1 概念 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO &#xff08; Last In First Out &#xff09;的原则…...

HarmonyOS开发:开源一个刷新加载组件

前言 系统Api中提供了下拉刷新组件Refresh&#xff0c;使用起来也是非常的好用&#xff0c;但是风格和日常的开发&#xff0c;有着巨大的出入&#xff0c;效果如下&#xff1a; 显然上面的效果是很难满足我们实际的需求的&#xff0c;奈何也没有提供的属性可以更改&#xff0c;…...