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

【Linux】生产者消费者模型 - 详解

目录

一.生产者消费者模型概念

1.为何要使用生产者消费者模型

2.生产者消费者之间的关系

3.生产者消费者模型的优点

二.基于阻塞队列的生产消费模型

1.在阻塞队列中的三种关系

2.BlockingQueue.hpp - 阻塞队列类

3.LockGurad.hpp - RAII互斥锁类

4.Task.hpp - 在阻塞队列中生产/消费的任务类

5.CPTest.cc - 测试

三.基于环形队列的生产消费模型 

1.在环形队列中的三种关系

2.环形队列vs阻塞队列

3.CircularQueue.hpp - 环形队列类

4.LockGuard.hpp - RAII互斥锁类

5.Task.cpp - 任务类

6.CPTest.cc - 测试

四. 写在最后

1.关于条件变量的伪唤醒问题

2.关于信号量和锁的先后顺序问题 


一.生产者消费者模型概念

1.为何要使用生产者消费者模型

生产者消费者模式就是通过一个"容器"来解决生产者与消费者之间的强耦合问题, 生产者和消费者彼此之间不直接通讯, 而是通过特定"容器"来进行通讯, 所以生产者生产完数据之后不需要等待消费者处理, 直接扔给"容器", 消费者不直接找生产者索要数据, 而是从"容器"中拿数据, "容器"其实就是由特定的数据结构编写的缓冲区, 平衡了生产者和消费者的处理能力, 通过生产消费之间的"容器"达到解耦的目的

2.生产者消费者之间的关系

一个场所, 也就是上述"容器"

二个角色, 生产者与消费者

三种关系

        生产者 - 生产者: 互斥关系

        消费者 - 消费者: 互斥关系

        生产者 - 消费者: 互斥关系, 同步关系

生产者消费者模型分为两种

1.单生产单消费

2.多生产多消费

3.生产者消费者模型的优点

1.将生产者消费者的强耦合关系解耦

2.支持并发

二.基于阻塞队列的生产消费模型

1.在阻塞队列中的三种关系

生产者与生产者之间互斥

消费者与消费者之间互斥

生产者与消费者之间既互斥又同步

同步关系使用条件变量

2.BlockingQueue.hpp - 阻塞队列类

#pragma once#include <iostream>
#include <queue>
#include "LockGurad.hpp"template <class T>
class BlockingQueue
{
public:static BlockingQueue<T> *GetInst(){return &_sInst;}public:void push(const T& in){LockGurad lock(&_mutex); // RAII锁// 如果阻塞队列满了就阻塞while(isFull()){pthread_cond_wait(&_isFullCond, &_mutex); // 已满, 阻塞}_bq.push(in);pthread_cond_signal(&_isEmptyCond); // 已经添加了一个, 唤醒已空条件阻塞}void pop(T* out){LockGurad lock(&_mutex); // RAII锁// 如果阻塞队列空了就阻塞while(isEmpty()){pthread_cond_wait(&_isEmptyCond, &_mutex); // 已空, 阻塞}*out = _bq.front();_bq.pop();pthread_cond_signal(&_isFullCond); // 已经取走了一个, 唤醒已满条件阻塞}~BlockingQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_isFullCond);pthread_cond_destroy(&_isEmptyCond);}private:bool isFull() // 判满{return _bq.size() == _capacity;}bool isEmpty() // 判空{return _bq.size() == 0;}private:std::queue<T> _bq;          // 阻塞队列size_t _capacity;           // 阻塞队列最大容量pthread_mutex_t _mutex;      // 保护阻塞队列的锁pthread_cond_t _isFullCond;  // 判断是否为满pthread_cond_t _isEmptyCond; // 判断是否为空private:// 单例模式BlockingQueue(size_t capacity = 5) : _capacity(capacity){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_isFullCond, nullptr);pthread_cond_init(&_isEmptyCond, nullptr);}BlockingQueue(const BlockingQueue<T> &copy) = delete;BlockingQueue<T> &operator=(const BlockingQueue<T> &copy) = delete;static BlockingQueue<T> _sInst;
};template <class T>
BlockingQueue<T> BlockingQueue<T>::_sInst;

3.LockGurad.hpp - RAII互斥锁类

#pragma once#include <pthread.h>class LockGurad
{
public:LockGurad(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGurad(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t* _mutex;
};

4.Task.hpp - 在阻塞队列中生产/消费的任务类

#pragma oncetypedef int(* func_t)(int, int);struct Task
{Task(){};Task(int x, int y, func_t func):_x(x),_y(y),_func(func){}int operator()(){return _func(_x, _y);}int _x;int _y;func_t _func;
};

5.CPTest.cc - 测试

#include "BlockingQueue.hpp"
#include "Task.hpp"
#include "time.h"
#include "stdlib.h"
#include "sys/types.h"
#include "unistd.h"int myAdd(int x, int y)
{return x + y;
}void *Consumer(void *args)
{BlockingQueue<Task> *bqsInst = (BlockingQueue<Task> *)args;while (1){// 消费任务Task t;bqsInst->pop(&t);std::cout << pthread_self() << " consumer: " << t._x << "+" << t._y << "=" << t() << std::endl;sleep(1);}return nullptr;
}void *Provider(void *args)
{BlockingQueue<Task> *bqsInst = (BlockingQueue<Task> *)args;while (1){// 制作任务int x = rand() % 100 + 1;int y = rand() % 100 + 1;Task t(x, y, myAdd);// 添加任务bqsInst->push(t);std::cout << pthread_self() << " productor: " << t._x << "+" << t._y << "=?" << std::endl;sleep(1);}return nullptr;
}int main()
{srand((unsigned int)time(nullptr) ^ getpid() ^ 0x123);// 获取单例指针BlockingQueue<Task> *bqsInst = BlockingQueue<Task>::GetInst();// 单生产单消费//pthread_t c, p;//pthread_create(&c, nullptr, Consumer, bqsInst);//pthread_create(&p, nullptr, Provider, bqsInst);// 多生产多消费pthread_t c[2], p[3];pthread_create(c, nullptr, Consumer, bqsInst);pthread_create(c + 1, nullptr, Consumer, bqsInst); pthread_create(p, nullptr, Provider, bqsInst);pthread_create(p + 1, nullptr, Provider, bqsInst);pthread_create(p + 2, nullptr, Provider, bqsInst);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);return 0;
}

三.基于环形队列的生产消费模型 

数据结构: 环形队列, 传送入口: http://t.csdn.cn/p1OXv

1.在环形队列中的三种关系

生产者与生产者之间互斥

消费者与消费者之间互斥

生产者与消费者之间同步

同步关系使用信号量

生产消费不需要互斥, 即分别用两把锁

2.环形队列vs阻塞队列

环形队列对比阻塞队列的优势: 生产与消费可以同时进行, 这是因为环形数据结构的特性, 以及信号量sem共同支持的

为什么?

对于阻塞队列而言

当阻塞队列中还存在有数据时, 生产者与消费者必然不会访问到同一个数据, 但是当队列为空, 如果不将生产与消费互斥的话, 就很可能在生产的同时又在消费, 消费的可能是正在生产中的数据, 就可能发生问题

对于环形队列而言

生产者与消费者的下标, 只有在环形队列满或空时指向同一位置, 通过信号量进行同步之后, 不可能会发生同时在同一位置生产消费的情况

生产消费指向同一位置, 只有两种情况

1.环形队列为满, 此时该位置是还存有未消费的数据, 没有多余的生产信号量, 该位置只可能存在消费行为

2.环形队列为空, 此时该位置不存有任何数据, 没有多余的消费信号量, 该位置只可能存在生产行为

3.CircularQueue.hpp - 环形队列类

#include <vector>
#include <semaphore.h>
#include "LockGurad.hpp"template <class T>
class CircularQueue
{
public:CircularQueue(size_t capacity) : _capacity(capacity), _cStep(0), _pStep(0){_v.resize(capacity);sem_init(&_spaceSem, 0, capacity);sem_init(&_dataSem, 0, 0);pthread_mutex_init(&_cMtx, nullptr);pthread_mutex_init(&_pMtx, nullptr);}void push(const T &in){// 这里把信号量加在锁前面, 进一步提高效率, 原理类似于买票看电影, 提前买票在排队检票效率更高sem_wait(&_spaceSem);{LockGurad lock(&_pMtx); // RAII加锁风格_v[_pStep++] = in;_pStep %= _capacity;}sem_post(&_dataSem);}void pop(T *out){sem_wait(&_dataSem);{LockGurad lock(&_cMtx);*out = _v[_cStep++];_cStep %= _capacity;}sem_post(&_spaceSem);}~CircularQueue(){sem_destroy(&_spaceSem);sem_destroy(&_dataSem);pthread_mutex_destroy(&_cMtx);pthread_mutex_destroy(&_pMtx);}private:std::vector<T> _v;     // 环形队列size_t _capacity; // 最大容量int _cStep; // 消费下标int _pStep; // 生产下标sem_t _spaceSem; // 空间资源信号量sem_t _dataSem;  // 数据资源信号量pthread_mutex_t _cMtx; // 生产锁pthread_mutex_t _pMtx; // 消费锁
};

4.LockGuard.hpp - RAII互斥锁类

#pragma once#include <pthread.h>class LockGurad
{
public:LockGurad(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGurad(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t* _mutex;
};

5.Task.cpp - 任务类

#pragma oncetypedef int(* func_t)(int, int);struct Task
{Task(){};Task(int x, int y, func_t func):_x(x),_y(y),_func(func){}int operator()(){return _func(_x, _y);}int _x;int _y;func_t _func;
};

6.CPTest.cc - 测试

#include <iostream>
#include "CircularQueue.hpp"
#include "Task.hpp"
#include <time.h>
#include <unistd.h>
#include <stdlib.h>int myAdd(int x, int y)
{return x + y;
}void* Consumer(void* args)
{CircularQueue<Task>* cq = (CircularQueue<Task>*)args;while(1){// 消费任务Task t;cq->pop(&t);std::cout << "消费: " << t._x << "+" << t._y << "=" << t() << std::endl;}
}void* Provider(void* args)
{CircularQueue<Task>* cq = (CircularQueue<Task>*)args;while(1){// 制作任务int x = rand() % 50 + 1;int y = rand() % 50 + 1;Task t(x, y, myAdd);// 添加任务cq->push(t);std::cout << "生产: " << t._x << "+" << t._y << "=?" << std::endl;sleep(1);}
}int main()
{srand((unsigned int)time(nullptr) ^ 0x123);CircularQueue<Task>* cq = new CircularQueue<Task>(5);pthread_t c[2], p[3];pthread_create(c, nullptr, Consumer, cq);pthread_create(c+1, nullptr, Consumer, cq);pthread_create(p, nullptr, Provider, cq);pthread_create(p+1, nullptr, Provider, cq);pthread_create(p+2, nullptr, Provider, cq);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);return 0;
}

四. 写在最后

1.关于条件变量的伪唤醒问题

为什么不用if, 而使用while, 原因就是如果此时只有1个资源, 而刚刚有很多个线程在挂起等待, 为了这1个资源全部被唤醒了, 那么就一定只有一个线程真正的拿到资源, 其他线程都是伪唤醒, 避免这种问题的方法就是用while判断, 当线程被唤醒时再循环判断一次, 如果为假就会跳出循环, 如果为真说明是伪唤醒, 则继续挂起等待

2.关于信号量和锁的先后顺序问题 

值得一提的是, 环形队列中生产者消费者的同步关系由信号量来实现, 并且生产者与消费者之间没有互斥关系, 那么生产者与消费者分别使用两把锁维护其各自的互斥关系, 那么有一个值得思考的问题就是信号量和锁谁在前谁在后问题

 答案是: 信号量先申请比较好. 信号量确实访问了临界资源, 但其内部实现也是原子性的, 而且信号量类似于预定机制, 只是提前把资源预定下来, 想要访问资源, 只需要等待取走锁资源即可

相关文章:

【Linux】生产者消费者模型 - 详解

目录 一.生产者消费者模型概念 1.为何要使用生产者消费者模型 2.生产者消费者之间的关系 3.生产者消费者模型的优点 二.基于阻塞队列的生产消费模型 1.在阻塞队列中的三种关系 2.BlockingQueue.hpp - 阻塞队列类 3.LockGurad.hpp - RAII互斥锁类 4.Task.hpp - 在阻塞队…...

源码深度解析Spring Bean的加载

在应用spring 的过程中&#xff0c;就会涉及到bean的加载&#xff0c;bean的加载经历一个相当复杂的过程&#xff0c;bean的加载入口如下&#xff1a; 使用getBean&#xff08;&#xff09;方法进行加载Bean&#xff0c;最终调用的是AbstractBeanFactory.doGetBean() 进行Bean的…...

STL——priority_queue

一、priority_queue介绍及使用 1.priority_queue文档介绍 &#xff08;1&#xff09;优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的。 &#xff08;2&#xff09;此上下文类似与堆&#xff0c;在堆中可以…...

Springboot集成工作流Activity

介绍 官网&#xff1a;https://www.activiti.org/ 一 、工作流介绍 1.工作流&#xff08;workflow&#xff09; 就是通过计算机对业务流程自动化执行管理&#xff0c;它主要解决的是“使在多个参与这之间按照某种预定义规则自动化进行传递文档、信息或任务的过程&#xff0c…...

2023软件测试工程师涨薪攻略,3年如何达到月薪30K?

1.软件测试如何实现涨薪 首先涨薪并不是从8000涨到9000这种涨薪&#xff0c;而是从8000涨到15K加到25K的涨薪。基本上三年之内就可以实现。 如果我们只是普通的有应届毕业生或者是普通本科那我们就只能从小公司开始慢慢往上走。 有些同学想去做测试&#xff0c;是希望能够日…...

Java面试——Spring Bean相关知识

目录 1.Bean的定义 2.Bean的生命周期 3.BeanFactory及Factory Bean 4.Bean的作用域 5.Bean的线程安全问题 1.Bean的定义 JavaBean是描述Java的软件组件模型。在Java模型中&#xff0c;通过JavaBean可以无限扩充Java程序的功能&#xff0c;通过JavaBean的组合可以快速的生…...

上班在群里摸鱼,逮到一个字节8年测试开发,聊过之后羞愧难当...

老话说的好&#xff0c;这人呐&#xff0c;一旦在某个领域鲜有敌手了&#xff0c;就会闲得某疼。前几天我在上班摸鱼刷群的时候认识了一位字节测试开发大佬&#xff0c;在字节工作了8年&#xff0c;因为本人天赋比较高&#xff0c;平时工作也兢兢业业&#xff0c;现在企业内有一…...

HTTP、WebSocket和Socket.IO

一、HTTP协议 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;。HTTP 协议和 TCP/IP 协议族内的其他众多的协议相同&#xff0c; 用于客户端和服务器之间的通信。请求访问文本或图像等资源的一端称为客户端&#xff0c; 而提供资源响应的一端称…...

Fluent Python 笔记 第 11 章 接口:从协议到抽象基类

本章讨论的话题是接口:从鸭子类型的代表特征动态协议&#xff0c;到使接口更明确、能验证实现是否符合规定的抽象基类(Abstract Base Class&#xff0c;ABC)。 11.1 Python 文化中的接口和协议 对 Python 程序员来说&#xff0c;“X 类对象”“X 协 议”和“X 接口”都是一个…...

【Spark分布式内存计算框架——Spark Core】11. Spark 内核调度(下)

8.5 Spark 基本概念 Spark Application运行时&#xff0c;涵盖很多概念&#xff0c;主要如下表格&#xff1a; 官方文档&#xff1a;http://spark.apache.org/docs/2.4.5/cluster-overview.html#glossary Application&#xff1a;指的是用户编写的Spark应用程序/代码&#x…...

Java中的函数

1.String.trim() : 主要有2个用法&#xff1a; 1、就是去掉字符串中前后的空白&#xff1b;这个方法的主要可以使用在判断用户输入的密码之类的。 2、它不仅可以去除空白&#xff0c;还可以去除字符串中的制表符&#xff0c;如 ‘\t’,\n等。 2.Integer.parseInt() : 字符串…...

实验6-霍纳法则及变治技术

目录 1.霍纳法则(Horners rule) 2.堆排序 3.求a的n次幂 1.霍纳法则(Horners rule) 【问题描述】用霍纳法则求一个多项式在一个给定点的值 【输入形式】输入三行,第一行是一个整数n,表示的是多项式的最高次数;第二行多项式的系数组P[0...n](从低到高存储);第三行是…...

IP地址:揭晓安欣警官自证清白的黑科技

《狂飙》这部电视剧&#xff0c;此从播出以来可谓是火爆了&#xff0c;想必大家都是看过的。剧中&#xff0c;主人公“安欣”是一名警察。一直在与犯罪分子做斗争。 莽村的李顺案中&#xff0c;有匿名者这个案件在网上发帖恶意造谣&#xff0c;说安欣是黑恶势力的保护伞&#…...

考研复试机试 | C++

目录1.盛水最多的容器<11>题目代码&#xff1a;2.整数转罗马数字题目&#xff1a;代码&#xff1a;3. 清华大学机试题 abc题目题解4.清华大学机试题 反序数题目描述代码对称平方数题目代码&#xff1a;5. 杭电上机题 叠筐题目&#xff1a;代码pass&#xff1a;关于清华大…...

第四章.误差反向传播法—误差反向传播法实现手写数字识别神经网络

第四章.误差反向传播法 4.3 误差反向传播法实现手写数字识别神经网络 通过像组装乐高积木一样组装第四章中实现的层&#xff0c;来构建神经网络。 1.神经网络学习全貌图 1).前提&#xff1a; 神经网络存在合适的权重和偏置&#xff0c;调整权重和偏置以便拟合训练数据的过程称…...

IB学习者的培养目标有哪些?

IB课程强调要培养年轻人的探究精神&#xff0c;在富有渊博知识的同时&#xff0c;更要勤于思考&#xff0c;敢于思考&#xff0c;尊重和理解跨文化的差异&#xff0c;坚持原则维护公平&#xff0c;让这个世界充满爱与和平&#xff0c;让这个世界变得更加美好。上一次我们为大家…...

C++类基础(十三)

类的继承 ● 通过类的继承&#xff08;派生&#xff09;来引入“是一个”的关系&#xff08; 17.2 — Basic inheritance in C&#xff09; – 通常采用 public 继承&#xff08; struct V.S. class &#xff09; – 注意&#xff1a;继承部分不是类的声明 – 使用基类的指针…...

03 OpenCV图像运算

文章目录1 普通加法1 加号相加2 add函数2 加权相加3 按位运算1 按位与运算2 按位或运算、非运算4 掩膜1 普通加法 1 加号相加 在 OpenCV 中&#xff0c;图像加法可以使用加号运算符&#xff08;&#xff09;来实现。例如&#xff0c;如果要将两幅图像相加&#xff0c;可以使用…...

【C语言学习笔记】:动态库

一、动态库 通过之前静态库那篇文章的介绍。发现静态库更容易使用和理解&#xff0c;也达到了代码复用的目的&#xff0c;那为什么还需要动态库呢&#xff1f; 1、为什么还需要动态库&#xff1f; 为什么需要动态库&#xff0c;其实也是静态库的特点导致。 ▶ 空间浪费是静…...

Zookeeper

zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制&#xff0c;防止出现脏读&#xff0c;例如我们常说的分布式锁。 zookeeper中的数据是存储在内存当中的&#xff0c;因此它的效率十分高效。它内部的存储方式十分类似于文件存储…...

wav转mp3,wav转换成mp3教程

很多使用音频文件的小伙伴&#xff0c;总会接触到不同类型的音频格式&#xff0c;根据需求不同需要做相关的处理。比如有人接触到了wav格式的音频&#xff0c;这是windows系统研发的一种标准数字音频文件&#xff0c;是一种占用磁盘体积超级大的音频格式&#xff0c;通常用于录…...

springboot项目配置文件加密

1背景&#xff1a; springboot项目中要求不能采用明文密码&#xff0c;故采用配置文件加密. 目前采用有密码的有redis nacos rabbitmq mysql 这些配置文件 2技术 2.1 redis nacos rabbitmq 配置文件加密 采用加密方式是jasypt 加密 2.1.1 加密步骤 2.1.2 引入maven依赖 …...

公司招聘:33岁以上的和两年一跳的不要,开出工资我还以为看错了...

导读&#xff1a;对于公司来说&#xff0c;肯定是希望花最少的钱招到最优秀的员工&#xff0c;但事实上这个想法是不太现实的&#xff0c;虽然如今互联网不太好找工作&#xff0c;但要员工降薪去入职&#xff0c;相信还是有很大难度的&#xff0c;很多人宁可在家休息&#xff0…...

【置顶】:文章合集系列

【置顶】&#xff1a;文章合集系列 必看 文章中的所有内容仅供做个人学习使用&#xff0c;所有环境都在本地搭建并验证&#xff0c;任何人使用文中方法进行未经授权的渗透行为都与文章与我本人无关&#xff0c;请各位大佬不要进行未经授权的渗透行为…… 前言 之前更新过一段…...

Go的web开发Gin框架1(八)——Gin

一、重点内容&#xff1a; 知识要点有哪些&#xff1f; 1、了解Gin框架 2、导入使用Gin框架 3、尝试配合GORM开发 4、整合html&#xff0c;css&#xff0c;js 二、详细知识点介绍&#xff1a; 1、Gin框架介绍 ​ Gin是一个golang的微框架&#xff0c;封装比较优雅&…...

吴思进——复杂美创始人首席执行官

杭州复杂美科技有限公司创始人兼CEO, 本科毕业于浙江大学机械专业&#xff0c;辅修过多门管理课程&#xff1b;1997年获经济学硕士学位&#xff0c;有关对冲基金的毕业论文被评为优秀&#xff1b;2008年创办杭州复杂美科技有限公司。 吴思进 中国电子学会区块链委员会专家&…...

apk简单介绍(组成以及打包安装流程)

apk简单介绍APK 的组成apk安装流程app的启动过程apk打包流程AIDLAIDL介绍为什么要设计这门语言它有哪些语法&#xff1f;默认支持的数据类型包括什么是apk打包流程了解打包流程能做什么操作APK 的组成 APK 其实是一个 zip 类型的压缩包&#xff0c;而一个典型的 APK 通常都会包…...

ffmpeg学习笔记之SDL视频播放器

看了雷神的 100行代码实现最简单的基于FFMPEGSDL的视频播放器&#xff08;SDL1.x&#xff09; 后手痒难耐&#xff0c;决定将里面的代码重新建一个 首先建立一个空项目&#xff0c;新建一个Mysimplest.cpp的文件。在里面写代码 #include <stdio.h>extern "C" …...

【Git】合并多条 commit 注释信息

文章目录1、查看 commit 记录2、合并 commit 注释1、查看 commit 记录 # 3 指的是查看最近 3 次的 commit 记录&#xff0c;如果要查看多次的可以修改数字 # -3 不加&#xff0c;则表示查看所有 commit 记录&#xff0c;一般还是用数字去指定 git log -32、合并 commit 注释 …...

【gcc/g++】程序的翻译(.c -->.exe)

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;前言我们在写完代码运行时会发现生成了一个.exe的可执行程序&#xff0c;那么该程序是如何形成的呢&#xff1f;本次章节将在linux下用编译器gcc进行一…...

学院网站建设方案/石家庄最新新闻事件

稍有接触过 WordPress 主题或插件制作修改的朋友&#xff0c;对 WordPress 的Hook机制应该不陌生&#xff0c;但通常刚接触WordPress Hook 的新手&#xff0c;对其运作原理可能会有点混乱或模糊。本文针对 WordPress Hook 运作大致做个简单的说明&#xff0c;而预设读者是理解基…...

做微课常用的网站有哪些/福州百度推广排名优化

插件描述&#xff1a;clock.js是一款简单的HTML5模拟时钟jQuery插件。该HTML5模拟时钟基于Canvas制作&#xff0c;有3种内置的主题&#xff0c;它带有时钟表盘界面和数字刻度&#xff0c;简单实用。使用方法使用该时钟插件需要在页面中引入jquery和clock.js文件。HTML结构可以实…...

有什么网站可以免费看电影/seo搜索优化工程师招聘

现在&#xff0c;老百姓对X线、CT和核磁共振都比较熟悉&#xff0c;去医院看病经常需要做这些检查。经常会有病人说&#xff1a;医生&#xff0c;请给我做最贵的、最好的检查。但是最贵的、花钱最多的检查就一定是最好的吗&#xff1f;对病人的疾病判断就是最清楚的吗&#xff…...

做网站专题模板/网络推广的含义

当联通大肆利用人们对iPhone的狂热攻城掠地的时候&#xff0c;移动在做什么呢&#xff1f;韬光养晦吗&#xff0c;肯定不会&#xff1b;中国移动正在一步一步走向对手机互联网和移动互联的全面掌控。而OPhone的发展之路&#xff0c;就是这个历程中一项非常重要的里程碑。工信部…...

wordpress输入框长度/企业网站推广的形式有

Arduino扩展库&#xff08;Library&#xff09;就是别人写好的&#xff0c;可重用的函数或类。 在之前的博文&#xff1a; Arduino学习(五) 蜂鸣器实验 中&#xff0c;我们学习了使用无源蜂鸣器可以发出不同频率的声音&#xff0c;据此&#xff0c;Arduino可以用来播放音乐了。…...

商铺设计/5g站长工具seo综合查询

人类历史上的出行方式经历了漫长的发展历程&#xff0c;从最早的步行、骑马、驾驶马车&#xff0c;到现代的汽车、高铁和飞机等交通工具&#xff0c;人类的出行方式逐步得到了革命性的改变。本文将从人类出行的演变、交通工具的发展以及未来出行的趋势等方面进行探讨&#xff0…...