【Linux】生产者消费者模型——阻塞队列BlockQueue

> 作者:დ旧言~
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解【Linux】生产者消费者模型——阻塞队列BlockQueue。

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:Linux初阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕

​​

🌟前言

Linux有两个重要的模型,一个是生产者消费者模型——阻塞队列BlockQueue,另一个则是生产者消费者模型——环形队列RingQueue。今天我们学习其中一个模型:【Linux】生产者消费者模型——阻塞队列BlockQueue。

⭐主体

学习【Linux】生产者消费者模型——阻塞队列BlockQueue咱们按照下面的图解:

​🌙 生产者消费者模型


💫 生产者消费者模型的概念

概念:

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。

生产者和消费者彼此之间不进行直接通讯,而通过这个容器来通讯,所以生产者生产完数据之后不用等待消费者处理,直接将生产的数据放到这个容器中,消费者也不用找生产者要数据,而是直接从容器也就是阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  • 这个阻塞队列就是用来给生产者和消费者解耦的
  • 如果缓冲区已经满了,则生产者线程阻塞;
  • 如果缓冲区为空,那么消费者线程阻塞。

图解:

💫 生产者消费者模型的特点

生产者消费者是多线程同步与互斥的一个经典场景,其特点如下:

  • 三种关系:生产者和生产者(互斥关系),生产者和消费者(互斥关系),生产者和消费者(互斥关系,同步关系)
  • 两种角色:生产者和消费者(通常由进程或线程承担)
  • 一个交易场所:通常指的是内存中的一段缓冲区。

生产者和生产者,消费者和消费者,生产者和消费者,它们之间为什么会存在互斥关系?

  • 介于生产者和消费者之间的容器可能会被多个执行流同时访问,因此我们需要将该临界资源用互斥锁保护起来。
  • 其中,所有的生产者和消费者都会竞争式的申请锁,因此生产者和生产者,消费者和消费者,生产者和消费者之间都存在互斥关系。

生产者和消费者之间为什么会存在同步关系?

  •  如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。
  • 反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。
  • 虽然这样不会造成任何数据不一致的问题,但是这样会引起另一方的饥饿问题,是非常低效的。我们应该让生产者和消费者访问该容器时具有一定的顺序性,比如让生产者先生产,然后再让消费者进行消。

注意:

互斥关系保证的是数据的正确性,而同步关系是为了让多线程之间协同起来。 

💫 生产者消费者模型优点

  • 解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
  • 支持并发:生产者直接调用消费者的某个方法,还有另一个弊端,由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直在等待。玩意消费者处理数据很慢,生产者就会白白浪费时间,使用了生产者/消费者模型后,生产者和消费者可以是两个独立的开发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据,基本上不用依赖消费者的处理速度。
  • 支持忙闲不均:缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造块的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造商速度慢下来,消费者再慢慢处理。

​🌙 基于Blockqueue的生产和消费模型

阻塞队列:阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。

  • 阻塞队列为空时,从阻塞队列中获取元素的线程将被阻塞,直到阻塞队列被放入元素。
  • 阻塞队列已满时,往阻塞队列放入元素的线程将被阻塞,直到有元素被取出。

图解:

💫 单生产单消费计算


1.随机数

下面以单生产单消费为例子:

//BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =5;
template <class T>
class BlockQueue
{
public:BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_pcond,nullptr);pthread_cond_init(&_ccond,nullptr);}void push(const T& in){pthread_mutex_lock(&_mutex);while(is_full()){pthread_cond_wait(&_pcond,&_mutex);//因为生产条件不满足无法生产,此时我们的生产者进行等待}_q.push(in);//pthread_cond_signal:这个函数可以放在临界区内部,也可以放在外部pthread_cond_signal(&_ccond);pthread_mutex_unlock(&_mutex);//pthread_cond_signal(&_ccond);}void pop(T*out)//输出型参数,*,输入输出型:&{pthread_mutex_lock(&_mutex);while(is_empty()){pthread_cond_wait(&_ccond,&_mutex);}*out = _q.front();_q.pop();pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}
private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size()==_maxcap;}
private:std::queue<T> _q;int _maxcap;//队列上限pthread_mutex_t _mutex;pthread_cond_t _pcond;//生产者条件变量pthread_cond_t _ccond;//消费者条件变量
};//mainCp.cc
void* consumer(void * bq_)
{BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(bq_);while(true){int data;bq->pop(&data);std::cout<<"消费数据: "<<data<<std::endl;sleep(1);}return nullptr;
}
void*productor(void*bq_)
{BlockQueue<int>*bq = static_cast<BlockQueue<int>*>(bq_);while(true){int data = rand()%10+1;bq->push(data);std::cout<<"生产数据: "<<data<<std::endl;}return nullptr;
}
int main()
{srand((unsigned long)time(nullptr)^getpid());BlockQueue<int> *bq = new  BlockQueue<int>();pthread_t c,p;pthread_create(&c,nullptr,consumer,bq);pthread_create(&p,nullptr,productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

总结分析:

pthread_cond_wait函数的第二个参数必须是我们正在使用的互斥锁,满了就会进行等待,如果像之前一样把锁拿走,那么其他线程就无法访问共享资源。

  • a.pthread_cond_wait:该函数调用的时候,会以原子性的方式,将锁释放,并将自己挂起
  • b.pthread_cond_wait:该函数在被唤醒返回的时候,会自动的重新获取你传入的锁

pthread_cond_signal伪唤醒:判断的问题:假设生产者有10个,消费者只有一个,消费一下数据,如果是pthread_cond_broadcast是把10个线程同时唤醒,可是只需要生产一个数据,而同时把10个线程唤醒而如果是if判断的时候push就会出问题了。

如果生产者生产慢,消费者消费快生产一个消费一个,而且消费的都是最新的数据。

如果生产者生产快,消费者消费慢稳定后,消费一个生产一个。

2.计算器任务Task

Task.hpp:包含func_t的回调函数,这个函数就是进行数据计算的回调函数:

#pragma once
#include <iostream>
#include <functional>
#include <cstdio>
class Task
{using func_t = std::function<int(int,int,char)>;
public:Task(){}Task(int x,int y,char op,func_t func):_x(x),_y(y),_op(op),_callback(func){}std::string operator()(){int result = _callback(_x,_y,_op);char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);return buffer;}
private:int _x;int _y;char _op;func_t _callback;
};

BlockQueue.hpp:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap =500;
template <class T>
class BlockQueue
{
public:BlockQueue(const int&maxcap = gmaxcap):_maxcap(maxcap){pthread_mutex_init(&_mutex,nullptr);pthread_cond_init(&_pcond,nullptr);pthread_cond_init(&_ccond,nullptr);}void push(const T& in){pthread_mutex_lock(&_mutex);while(is_full()){pthread_cond_wait(&_pcond,&_mutex);}_q.push(in);pthread_cond_signal(&_ccond);pthread_mutex_unlock(&_mutex);}void pop(T*out){pthread_mutex_lock(&_mutex);if(is_empty()){pthread_cond_wait(&_ccond,&_mutex);}*out = _q.front();_q.pop();pthread_cond_signal(&_pcond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}
private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size()==_maxcap;}
private:std::queue<T> _q;int _maxcap;pthread_mutex_t _mutex;pthread_cond_t _pcond;pthread_cond_t _ccond;
};

Main.cc:

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "BlockQueue.hpp"
#include "Task.hpp"
const std::string oper = "+-*/%"; 
int mymath(int x,int y,char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}elseresult = x % y;}break;default:break;}return result;
}
void* consumer(void * bq_)
{BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(bq_);while(true){Task t;bq->pop(&t);std::cout<<"消费任务: "<<t()<<std::endl;}return nullptr;
}
void*productor(void*bq_)
{BlockQueue<Task>*bq = static_cast<BlockQueue<Task>*>(bq_);while(true){int x = rand()%100+1;int y = rand()%10;int operCode = rand()%oper.size();Task t(x,y,oper[operCode],mymath);bq->push(t);std::cout<<"生产任务: "<<t.toTaskString()<<std::endl;  sleep(1);}return nullptr;
}
int main()
{srand((unsigned long)time(nullptr)^getpid());BlockQueue<Task> *bq = new  BlockQueue<Task>();pthread_t c,p;pthread_create(&c,nullptr,consumer,bq);pthread_create(&p,nullptr,productor,bq);pthread_join(c,nullptr);pthread_join(p,nullptr);delete bq;return 0;
}

3.存储任务

定义结构体BlockQueues封装计算任务的阻塞队列和存储任务的阻塞队列,创建生产者线程,消费者线程,存储线程执行各自的方法:

blockqueue.hpp:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
const int gmaxcap = 500;
template <class T>
class BlockQueue
{   
public:BlockQueue(const int &maxcap = gmaxcap):_maxcap(maxcap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_pcond, nullptr);pthread_cond_init(&_ccond, nullptr);}void push(const T &in){pthread_mutex_lock(&_mutex);while(is_full()){pthread_cond_wait(&_pcond, &_mutex); }_q.push(in);pthread_cond_signal(&_ccond);pthread_mutex_unlock(&_mutex);}void pop(T *out){pthread_mutex_lock(&_mutex);while(is_empty()){pthread_cond_wait(&_ccond, &_mutex);}*out = _q.front();_q.pop();pthread_cond_signal(&_pcond); pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_pcond);pthread_cond_destroy(&_ccond);}
private:bool is_empty(){return _q.empty();}bool is_full(){return _q.size() == _maxcap;}
private:std::queue<T> _q;int _maxcap;pthread_mutex_t _mutex;pthread_cond_t _pcond; pthread_cond_t _ccond;
};

Task.hpp:

#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <functional>class CalTask
{using func_t = std::function<int(int,int,char)>;// typedef std::function<int(int,int)> func_t;
public:CalTask(){}CalTask(int x, int y, char op, func_t func):_x(x), _y(y), _op(op), _callback(func){}std::string operator()(){int result = _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);return buffer;}
private:int _x;int _y;char _op;func_t _callback;
};const std::string oper = "+-*/%";int mymath(int x, int y, char op)
{int result = 0;switch (op){case '+':result = x + y;break;case '-':result = x - y;break;case '*':result = x * y;break;case '/':{if (y == 0){std::cerr << "div zero error!" << std::endl;result = -1;}elseresult = x / y;}break;case '%':{if (y == 0){std::cerr << "mod zero error!" << std::endl;result = -1;}elseresult = x % y;}break;default:// do nothingbreak;}return result;
}class SaveTask
{typedef std::function<void(const std::string&)> func_t;
public:SaveTask(){}SaveTask(const std::string &message, func_t func): _message(message), _func(func){}void operator()(){_func(_message);}
private:std::string _message;func_t _func;
};void Save(const std::string &message)
{const std::string target = "./log.txt";FILE *fp = fopen(target.c_str(), "a+");if(!fp){std::cerr << "fopen error" << std::endl;return;}fputs(message.c_str(), fp);fputs("\n", fp);fclose(fp);
}

MianCp.cc:

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <ctime>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class K>
struct BlockQueues
{BlockQueue<C> *c_bq;BlockQueue<K> *s_bq;
};
void* productor(void*args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;while(true){int x = rand()%10+1;int y = rand()%5+1;int operCode = rand()%oper.size();CalTask t(x,y,oper[operCode],mymath);bq->push(t);std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;sleep(1);}return nullptr;
}
void* consumer(void*args)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->c_bq;BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;while(true){CalTask t;bq->pop(&t);std::string result = t();std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;SaveTask save(result,Save);save_bq->push(save);std::cout<<"cal thread,推送存储任务完成..."<<std::endl;}return nullptr;
}
void*saver(void*args)
{BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(args))->s_bq;while(true){SaveTask t;save_bq->pop(&t);t();std::cout<<"save thread,保存任务完成..."<<std::endl;}return nullptr;
}
int main()
{srand((unsigned int)time(nullptr)^getpid());BlockQueues<CalTask,SaveTask> bqs;bqs.c_bq = new BlockQueue<CalTask>();bqs.s_bq = new BlockQueue<SaveTask>();pthread_t c,p,s;pthread_create(&p,nullptr,productor,&bqs);pthread_create(&c,nullptr,consumer,&bqs);pthread_create(&s,nullptr,saver,&bqs);pthread_join(c,nullptr);pthread_join(p,nullptr);pthread_join(s,nullptr);delete bqs.c_bq;delete bqs.s_bq;return 0;
}

💫 多生产多消费

只需要稍微改一改MainCp.cc即可完成多生产多消费,其他文件代码不需要更改

MainCp.cc:

#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include "blockqueue.hpp"
#include "Task.hpp"
template<class C,class S>
class BlockQueues
{
public:BlockQueue<C>*c_bq;BlockQueue<S>*s_bq;
}; 
void*productor(void*bqs_)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;while(true){int x = rand()%100+1;int y = rand()%10;int operCode = rand()%oper.size();CalTask t(x,y,oper[operCode],mymath);bq->push(t);std::cout<<"productor thread,生产计算任务: "<<t.toTaskString()<<std::endl;sleep(1);}return nullptr;
}
void* consumer(void * bqs_)
{BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->c_bq;BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;while(true){CalTask t;bq->pop(&t);std::string result = t();std::cout<<"cal thread,完成计算任务: "<<result<<" ... done"<<std::endl;// SaveTask save(result,Save);// save_bq->push(save);// std::cout<<"cal thread,推送存储任务完成..."<<std::endl;// sleep(1);}return nullptr;
}
void* saver(void* bqs_)
{BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask,SaveTask>*>(bqs_))->s_bq;while(true){SaveTask t;save_bq->pop(&t);t();std::cout<<"save thread,保存任务完成..."<<std::endl;}return nullptr;
};
int main()
{srand((unsigned long)time(nullptr)^getpid());BlockQueues<CalTask,SaveTask> bqs;bqs.c_bq = new  BlockQueue<CalTask>();bqs.s_bq = new  BlockQueue<SaveTask>();pthread_t c[2],p[3];pthread_create(p,nullptr,productor,&bqs);pthread_create(p+1,nullptr,productor,&bqs);pthread_create(p+2,nullptr,productor,&bqs);pthread_create(c,nullptr,consumer,&bqs);pthread_create(c+1,nullptr,consumer,&bqs);pthread_join(c[0],nullptr);pthread_join(c[1],nullptr);pthread_join(p[0],nullptr);pthread_join(p[1],nullptr); pthread_join(p[2],nullptr);delete bqs.c_bq;delete bqs.s_bq;return 0;
}

​🌙 总结

生产者消费模型高效在哪里?

高效体现在一个线程拿出来任务可能正在做计算,它在做计算的同时,其他线程可以继续从队列中拿,继续做运算,高效并不是体现在从队列中拿数据高效!而是我们可以让一个、多个线程并发的同时计算多个任务!在计算多个任务的同时,并不影响其他线程,继续从队列里拿任务的过程。也就是说,生产者消费者模型的高效:可以在生产之前与消费之后让线程并行执行,不要认为生产者消费模式仅仅只是把任务生产到队列的过程就是生产过程,生产过程:1.拿任务、需要费点劲2.拿到后再放到队列里面整个一体,整个生产的过程;整个消费的过程:不是把任务拿到线程的上下文中就完了,拿到之后还要进行计算或存储这些工作才是消费的过程在生产前和和消费后我们多个线程是可以并发的。

🌟结束语 

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

​​​ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/346690.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络安全】网络安全基础精讲 - 网络安全入门第一篇

目录 一、网络安全基础 1.1网络安全定义 1.2网络系统安全 1.3网络信息安全 1.4网络安全的威胁 1.5网络安全的特征 二、入侵方式 2.1黑客 2.1.1黑客入侵方式 2.1.2系统的威胁 2.2 IP欺骗 2.2.1 TCP等IP欺骗 2.2.2 IP欺骗可行的原因 2.3 Sniffer探测 2.4端口扫描技术…

知识付费小程序源码系统 一键拥有属于自己的知识店铺 带完整的安装代码包以及搭建教程

系统概述 在数字化时代&#xff0c;知识已成为最具价值的资产之一。随着互联网技术的飞速发展&#xff0c;知识付费市场迎来了前所未有的发展机遇。为了帮助广大内容创作者、教育机构及个人轻松搭建专属的知识店铺&#xff0c;一款高效、易用的知识付费小程序源码系统应运而生…

架构设计-用户信息及用户相关的密码信息设计

将用户的基本信息和用户密码存放在不同的数据库表中是一种常见的安全做法&#xff0c;这种做法旨在增强数据的安全性和管理的灵活性。以下是这种做法的几个关键原因&#xff1a; 安全性增强&#xff1a; 当用户密码被单独存放在一个表中时&#xff0c;可以使用更强大的加密和哈…

超详解——Python模块文档——基础篇

目录 1. Unix起始行 示例&#xff1a; 2. 对象和类型 示例&#xff1a; 3. 一切都是对象 示例&#xff1a; 4. 理解对象和引用 示例&#xff1a; 5. 理解对象和类型 示例&#xff1a; 6. 标准类型 示例&#xff1a; 7. 其他内建类型 示例&#xff1a; 8. 类型的类…

东南亚电商Tiki、Qoo10:如何用自养号测评提升产品曝光和销量

随着互联网的普及和全球化的推进&#xff0c;跨境电商在东南亚地区日益繁荣。Tiki、Qoo10作为该地区的电商巨头&#xff0c;不仅吸引了大量消费者&#xff0c;也成为了卖家竞相角逐的战场。为了在这场竞争中脱颖而出&#xff0c;卖家们纷纷采用测评这一方式来提升产品销量。本文…

弱智吧”,人类抵御AI的最后防线

“写遗嘱的时候错过了deadline怎么办&#xff1f;” “怀念过去是不是在时间的长河里刻舟求剑&#xff1f;” “英语听力考试总是听到两个人在广播里唠嗑&#xff0c;怎么把那两个干扰我做题的人赶走&#xff1f;” 以上这些饱含哲学但好像又莫名其妙的问题&#xff0c;出自…

MySQL复习题(期末考试)

MySQL复习题&#xff08;期末考试&#xff09; 1.MySQL支持的日期类型&#xff1f; DATE,DATETIME,TIMESTAMP,TIME,TEAR 2.为表添加列的语法&#xff1f; alter table 表名 add column 列名 数据类型; 3.修改表数据类型的语法是&#xff1f; alter table 表名 modify 列名 新…

在windows10 安装子系统linux(WSL安装方式)

在 windows 10 平台采用了WSL安装方式安装linux子系统 1 查找自己想要安装的linux子系统 wsl --list --online 2 在线安装 个人用Debian比较多&#xff0c;这里选择Debian&#xff0c;如下图&#xff1a; wsl --install -d Debian 安装过程中有一步要求输入用户名与密码&…

LNMP与动静态网站介绍

Nginx发展 Nginx nginx http server Nginx是俄罗斯人 Igor Sysoev(伊戈尔.塞索耶夫)开发的一款高性能的HTTP和反向代理服务器。 Nginx以高效的epoll.kqueue,eventport作为网络IO模型&#xff0c;在高并发场景下&#xff0c;Nginx能够轻松支持5w并发连接数的响应&#xff0c;并…

【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【Go语言精进之路】 文章目录 引言一、切片究竟是什么&#xff1f;1.1 基础的创建数组示例1.2 基础的创建切片示例1.3 切片与数组的关系 二、切片的高级特性&#xff1a;动态扩容2.1 使用 append …

分布式一致性理论

分布式一致性理论 1.数据库事务ACID理论 为保证事务正确可靠而必须具备的四个核心特性。这四个特性分别是&#xff1a;原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xff08;D…

社交创新:Facebook的技术与产品发展

在当今数字化时代&#xff0c;社交网络已经渗透到我们生活的方方面面&#xff0c;成为了人们日常交流、信息获取和社交互动的主要方式。而在这个众多社交平台中&#xff0c;Facebook作为其中的佼佼者&#xff0c;其技术与产品的发展历程也是一个社交创新的缩影。本文将探索Face…

六、【源码】SQL执行器的定义和实现

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/06-sql-executor SQL执行器的定义和实现 之前的Sql执行都是耦合在SqlSession里的&#xff0c;现在要对这部分进行解耦和重构&#xff0c;引…

《软件定义安全》之三:用软件定义的理念做安全

第3章 用软件定义的理念做安全 1.不进则退&#xff0c;传统安全回到“石器时代” 1.1 企业业务和IT基础设施的变化 随着企业办公环境变得便利&#xff0c;以及对降低成本的天然需求&#xff0c;企业始终追求IT集成设施的性价比、灵活性、稳定性和开放性。而云计算、移动办公…

FiRa标准UWB MAC实现(三)——距离如何获得?

继续前期FiRa MAC相关介绍,将FiRa UWB MAC层相关细节进一步进行剖析,介绍了UWB技术中最重要的一个点,高精度的距离是怎么获得的,具体使用的测距方法都有哪些,原理又是什么。为后续FiRa UWB MAC的实现进行铺垫。 3、测距方法 3.1 SS-TWR SS-TWR为Single-Sided Two-Way Ra…

如何系统学习vue框架

前言 在软件开发的浩渺星海中&#xff0c;编程规范如同航海的罗盘&#xff0c;为我们指引方向&#xff0c;确保我们的代码之旅能够顺利、高效地到达目的地。无论是个人开发者还是大型团队&#xff0c;编程规范都是提升代码质量、保障项目成功不可或缺的一环。 因此&#xff0c…

人工智能模型对有争议的话题持相反的观点

人工智能模型对有争议的话题持相反的观点 并非所有生成式人工智能模型都是平等的&#xff0c;特别是当涉及到它们如何处理两极分化的主题时。 在2024年ACM公平、问责和透明度(FAccT)会议上发表的一项最新研究中&#xff0c;卡内基梅隆大学、阿姆斯特丹大学和人工智能初创公司h…

使用seq2seq架构实现英译法

seq2seq介绍 模型架构&#xff1a; Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种在自然语言处理&#xff08;NLP&#xff09;中广泛应用的架构&#xff0c;其核心思想是将一个序列作为输入&#xff0c;并输出另一个序列。这种模型特别适用于机器翻译、聊天…

携程无感验证

声明 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。 本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。 如有侵权,请联系我进行删除。 这里只是我分析的分析过程,以及一些重要点的记录…

Java从入门到放弃

线程池的主要作用 线程池的设计主要是为了管理线程&#xff0c;为了让用户不需要再关系线程的创建和销毁&#xff0c;只需要使用线程池中的线程即可。 同时线程池的出现也为性能的提升做出了很多贡献&#xff1a; 降低了资源的消耗&#xff1a;不会频繁的创建、销毁线程&…