【Linux】生产者消费者模型

news2024/10/5 19:17:53

文章目录

      • 1.生产者消费者模型
        • 1.1生产者消费者模型的特点
        • 1.2生产者消费者模型的原则
        • 1.3生产者消费者模型的优点
      • 2.基于阻塞队列的生产者消费者模型
        • 2.1如何理解生产者消费者模型的并发?
      • 3.信号量
        • 3.1信号量接口
        • 3.2基于环形队列的生产者消费者模型
        • 3.3信号量和条件变量的区别

1.生产者消费者模型

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

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

image-20221122205212166

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

生产者消费者是多线程同步互斥的一个经典场景。有以下的特点:

  • 生产者与消费者互斥的情况:生产者和消费者之间存在竞争锁的现象。这就是生产者和消费者的互斥关系。
  • 生产者与消费者同步的情况:如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。这就是同步现象
  • 生产者与生产者之间的关系,消费者与消费者之间的关系(商品缺少的极端情况)都是互斥的。

为什么生产者与消费者、生产者与生产者、消费者与消费者之间都存在互斥关系?

介于生产者和消费者之间的容器可能会被多个执行流同时访问,因此我们需要将该临界资源用互斥锁保护起来。

其中,所有的生产者和消费者都会竞争式的申请锁,因此生产者和生产者、消费者和消费者、生产者和消费者之间都存在互斥关系。

为什么生产者和消费者需要同步关系?

如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。这就是同步现象。

1.2生产者消费者模型的原则

以上三种可以总结为生产者与消费者模型的特点:321原则

  • 三种关系:生产者与生产者之间有互斥关系,消费者与消费者之间有互斥关系。生产者与消费者之间有互斥同步关系
  • 两种角色:生产者线程和消费者线程
  • 一个交易场所:可以是链表或者队列

1.3生产者消费者模型的优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

我们在主函数中调用某一函数,那么我们必须等该函数体执行完后才继续执行主函数的后续代码,因此函数调用本质上是一种紧耦合。

在生产者消费者模型中,生产者只负责生产数据并放入队列中,消费者只负责从队列中取走数据,并使用数据。因此生产者消费者模型本质是一种松耦合。

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

因为可能同时存在多个生产者和消费者,生产者生产的商品放入队列的尾部,消费者从队列的头部取走商品,因此需要两把锁对队列头和尾进行保护。

同时当队列不为空时,需要通知消费者进行消费,当队列为空时,需要通知生产者进行生产,因此需要两个条件变量分别通知生产者和消费者。

image-20221123130502756

基本框架

template <class T>
class Blockqueue{
public:
    void push(){//生产
        //加锁
        //判断队列是否满
        if(队列满了){//不生产,等待唤醒
            
        }
        else{//不满,生产
            
        }
        //解锁
    }
    void pop(){//消费
    	//加锁
        //判断是否为空
        if(为空){//不消费,等待唤醒
        	
        }
        else{//消费
            
        }
        //解锁
    }
private:
    queue<T> bq_;	//阻塞队列
    uint32_t cap_;	//容量
    pthread_mutex_t mutex_head;	//保护阻塞队列头部的互斥量
    pthread_mutex_t mutex_tail;	//保护阻塞队列尾部的互斥量
    pthread_cond_t concond_;	//让消费者等待的条件变量
    pthread_cond_t procond_;	//让生产者等待的条件
}

blockqueue.hpp

阻塞队列模板的实现。

#include <iostream>
#include <queue>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;
const uint32_t defaultcap=15;
template <class T>
class Blockqueue{
public:
    Blockqueue(uint32_t cap=defaultcap):cap_(cap)
    {
        pthread_cond_init(&concond_,nullptr);
        pthread_cond_init(&procond_,nullptr);
        pthread_mutex_init(&mutex_head,nullptr);
        pthread_mutex_init(&mutex_tail,nullptr);
    }
    ~Blockqueue(){
        pthread_cond_destroy(&concond_);
        pthread_cond_destroy(&procond_);
        pthread_mutex_destroy(&mutex_head);
        pthread_mutex_destroy(&mutex_tail);
    }
    void push(const T &in){//生产
        //加锁
        //判断队列是否满
        //队列满了,不生产,等待唤醒
        //队列不满,生产
        //解锁
        lockqueue(&mutex_tail);
        while(isfull()){    //如果满了,等待被唤醒生产
            prowaitblock();
        }
        pushCore(in);
        unlockqueue(&mutex_tail);
        //唤醒消费者
        wakecon();
    }
    T pop(){//消费
    	//加锁
        //判断是否为空
        //不消费,等待唤醒
        //消费
        //解锁
        lockqueue(&mutex_head);
        while(isempty()){
        	conwaitblock();
        }
        T tmp=popcore();
        unlockqueue(&mutex_head);
        wakepro();
        return tmp;
    }
private:
    void prowaitblock(){
        pthread_cond_wait(&procond_,&mutex_tail);
    }
    void conwaitblock(){
        pthread_cond_wait(&concond_,&mutex_head);
    }
    void wakecon(){
        pthread_cond_signal(&concond_);
    }
    void wakepro(){
        pthread_cond_signal(&procond_);
    }
    void lockqueue(pthread_mutex_t* mutexq){
        pthread_mutex_unlock(mutexq);
    }
    void unlockqueue(pthread_mutex_t* mutexq){
        pthread_mutex_unlock(mutexq);
    }
    bool isfull(){
        return bq_.size()==cap_;
    }
    bool isempty(){
        return bq_.empty();
    }
    void pushCore(const T &in){
        bq_.push(in);
    }
    T popcore(){
        T tmp=bq_.front();
        bq_.pop();
        return tmp;
    }
private:
    queue<T> bq_;	//阻塞队列
    uint32_t cap_;	//容量
    pthread_mutex_t mutex_head;	//保护阻塞队列头部的互斥量
    pthread_mutex_t mutex_tail;
    pthread_cond_t concond_;	//让消费者等待的条件变量
    pthread_cond_t procond_;	//让生产者等待的条件
};

task.hpp

阻塞队列存放的任务类

#pragma once
#include <iostream>
#include <string>
class Task
{
public:
    Task() : elemOne_(0), elemTwo_(0), operator_('0'){
    }
    Task(int one, int two, char op) : elemOne_(one), elemTwo_(two), operator_(op){
    }
    int operator() (){
        return run();
    }
    int run(){
        int result = 0;
        switch (operator_)
        {
        case '+':
            result = elemOne_ + elemTwo_;
            break;
        case '-':
            result = elemOne_ - elemTwo_;
            break;
        case '*':
            result = elemOne_ * elemTwo_;
            break;
        case '/':
        {
            if (elemTwo_ == 0){
                std::cout << "div zero, abort" << std::endl;
                result = -1;
            }
            else{
                result = elemOne_ / elemTwo_;
            }
        }
        break;
        case '%':
        {
            if (elemTwo_ == 0){
                std::cout << "mod zero, abort" << std::endl;
                result = -1;
            }
            else{
                result = elemOne_ % elemTwo_;
            }
        }
        break;
        default:
            std::cout << "非法操作: " << operator_ << std::endl;
            break;
        }
        return result;
    }
    int get(int *e1, int *e2, char *op){
        *e1 = elemOne_;
        *e2 = elemTwo_;
        *op = operator_;
    }
private:
    int elemOne_;
    int elemTwo_;
    char operator_;
};

测试程序

#include "blockqueue.hpp"
#include "task.hpp"
#include <ctime>
const std::string ops = "+-*/%";
void* consumer_run(void* arg){
    Blockqueue<Task>* blq=(Blockqueue<Task>*)arg;
    while(true){    //循环获取任务,并执行
        Task t=blq->pop();
        int result=t();
        int em1,em2;
        char op;
        t.get(&em1,&em2,&op);
        cout << "consumer[" << pthread_self() << "] " << (unsigned long)time(nullptr) 
        << " 消费了一个任务: " << em1 << op << em2 << "=" << result << endl;
    }
    return nullptr;
}
void* productor_run(void* arg){
    Blockqueue<Task>*blq=(Blockqueue<Task>*)arg;
    while (true)
    {
        int em1=rand()%100,em2=rand()%30;
        char op=ops[rand()%4];
        Task t(em1,em2,op);
        blq->push(t);
        cout << "producter[" << pthread_self() << "] " << (unsigned long)time(nullptr) 
        << " 生产了一个任务: " << em1 << op << em2 << "=?" << endl;
        sleep(1);
    }
    return nullptr;
}
int main(){
    srand((unsigned long)time(nullptr) ^ getpid());
    Blockqueue<Task> blq;   //创建一个阻塞队列
    pthread_t c1,c2,c3,c4,p1,p2,p3,p4,p5;
    pthread_create(&c1,nullptr,consumer_run,&blq);
    pthread_create(&c2,nullptr,consumer_run,&blq);
    pthread_create(&c3,nullptr,consumer_run,&blq);
    pthread_create(&c4,nullptr,consumer_run,&blq);
    pthread_create(&p1,nullptr,productor_run,&blq);
    pthread_create(&p2,nullptr,productor_run,&blq);
    pthread_create(&p3,nullptr,productor_run,&blq);
    pthread_create(&p4,nullptr,productor_run,&blq);
    pthread_create(&p5,nullptr,productor_run,&blq);
    //回收线程
    pthread_join(c1,nullptr);
    pthread_join(c2,nullptr);
    pthread_join(c3,nullptr);
    pthread_join(c4,nullptr);
    pthread_join(p1,nullptr);
    pthread_join(p2,nullptr);
    pthread_join(p3,nullptr);
    pthread_join(p4,nullptr);
    pthread_join(p5,nullptr);
    return 0;
}

程序执行的结果

image-20221123130913304

2.1如何理解生产者消费者模型的并发?

并发不是在临界区的并发,由于临界区存在锁(只有一个线程可以访问资源),因此不可能做到并发。

这里的并发是指:生产前(制造任务要花时间)和消费后(执行任务需要时间)的并发。

  • 多个生产者在同时生产任务;只不过在将任务放入队列的时候,同一时刻只有一个线程可以放入任务。
  • 多个消费者在同时执行任务;只不过在将任务取出的时候,同一时刻只有一个线程可以取出任务。

image-20221123141839306

3.信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步 。

  • 临界资源其实不一定只能一个线程同时访问的,如果我们的临界资源是可以看做多份的情况下,可能做到让多个线程同时访问的;只要访问的是不同的资源就可。
  • 任何线程,如果想访问临界资源中的某一个,一定必须先申请信号量,使用完毕,必须释放信号量
  • 申请信号量表示可以使用临界资源中的某一个
  • 信号量本质是一种资源的预定机制。

信号量是一种特殊的变量,它只能取自然数值并且只支持两种操作:等待(wait)和信号(signal)。但是在Linux/UNIX中,“等待”和“信号”都已经具有特殊的含义。所以我们用的称呼是P、V操作。

  • P操作(申请资源):如果信号量的值大于0,就减1。如果值为0,则挂起等待
  • V操作(发布资源):如果有其他进程因为等待SV而挂起,则唤醒之;如果没有,则将小信号量的值加1。

互斥量与信号量的关系?

  • 二元信号量就是互斥量。互斥量的值只能取0或者1。
  • 当信号量的初值为1,且在0和1之间变化时,成为二元信号量。其与互斥锁的区别在于:互斥锁有拥有者这一概念,信号量则没有。互斥锁由同一线程加放锁,信号量可以由不同线程进行PV操作。
  • 计数信号量允许多个线程,且值为剩余可用资源数量。互斥锁保证多个线程对一个共享资源的互斥访问,信号量用于协调多个线程对一系列资源的访问

3.1信号量接口

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

参数说明:

  • pshared:0表示线程间共享,非零表示进程间共享
  • value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1。当信号量为0时,挂起等待被唤醒
int sem_wait(sem_t *sem);

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);

3.2基于环形队列的生产者消费者模型

  • 环形队列采用数组模拟,用模运算来模拟环状特性
  • 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来,这里可以直接使用信号量代替计数器
  • 判断满或者空。另外也可以预留一个空的位置,作为满的状态

image-20221123133002609

ringqueue.hpp

环形队列的实现

#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <semaphore.h>
using namespace std;

const int gcap = 10;
template <class T>
class Ringqueue{
public:
    Ringqueue():ringqueue_(gcap),pIndex_(0),cIndex_(0)
    {
        pthread_mutex_init(&pmutex_,nullptr);
        pthread_mutex_init(&cmutex_,nullptr);
        sem_init(&roomsem_,0,ringqueue_.size());
        sem_init(&datasem_,0,0);
    }
    ~Ringqueue(){
        pthread_mutex_destroy(&pmutex_);
        pthread_mutex_destroy(&cmutex_);
        sem_destroy(&roomsem_);
        sem_destroy(&datasem_);
    }
    void push(const T& t){  //生产
        sem_wait(&roomsem_);
        pthread_mutex_lock(&pmutex_);
        ringqueue_[pIndex_]=t;
        pIndex_++;
        pIndex_%=ringqueue_.size();
        pthread_mutex_unlock(&pmutex_);
        sem_post(&datasem_);
    }
    T pop(){
        sem_wait(&datasem_);
        pthread_mutex_lock(&cmutex_);
        T tmp=ringqueue_[cIndex_];
        cIndex_++;
        cIndex_%=ringqueue_.size();
        pthread_mutex_unlock(&cmutex_);
        sem_post(&roomsem_);
        return tmp;
    }
private:
    vector<T> ringqueue_;	//阻塞队列
    uint32_t pIndex_;     // 当前生产者写入的位置, 如果是多线程,pIndex_也是临界资源
    uint32_t cIndex_;     // 当前消费者读取的位置,如果是多线程,cIndex_也是临界资源
    pthread_mutex_t pmutex_;	
    pthread_mutex_t cmutex_;
    sem_t roomsem_;
    sem_t datasem_;
};

接口的测试

#include "ringqueue.hpp"
#include <ctime>
#include <unistd.h>
#include"task.hpp"
const std::string ops = "+-*/%";

void *productor(void *args)
{
    Ringqueue<int> *rqp = static_cast<Ringqueue<int> *>(args);
    while(true)
    {
        int data = rand()%10;
        rqp->push(data);
        cout << "pthread[" << pthread_self() << "]" << " 生产了一个数据: " << data << endl;
        sleep(1);
    }
}

void *consumer(void *args)
{
    Ringqueue<int> *rqp = static_cast<Ringqueue<int> *>(args);
    while(true)
    {
        //sleep(10);
        int data = rqp->pop();
        cout << "pthread[" << pthread_self() << "]" << " 消费了一个数据: " << data << endl;
    }
}
int main()
{
    srand((unsigned long)time(nullptr)^getpid());
    Ringqueue<int> rq;
    pthread_t c1,c2,c3, p1,p2,p3;
    pthread_create(&p1, nullptr, productor, &rq);
    pthread_create(&p2, nullptr, productor, &rq);
    pthread_create(&p3, nullptr, productor, &rq);
    pthread_create(&c1, nullptr, consumer, &rq);
    pthread_create(&c2, nullptr, consumer, &rq);
    pthread_create(&c3, nullptr, consumer, &rq);
    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);
    pthread_join(p3, nullptr);
    return 0;
}

执行结果:

image-20221123142140108

3.3信号量和条件变量的区别

信号量利用条件变量、互斥锁、计数器实现,计数器就是信号量的核心,信号量是条件变量的高级抽象

区别:

  • (1)使用条件变量可以一次唤醒所有等待者(广播),而这个信号量没有的功能。
  • (2)信号量是有一个值(状态的),而条件变量是没有的,没有地方记录唤醒(发送信号)过多少次,也没有地方记录唤醒线程(wait返回)过多少次。从实现上来说一个信号量可以是用mutex + counter + condition variable实现的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git_GitHub——基本操作、创建远程库、远程库操作、团队协作、SSH免密登录

网址:GitHub: Let’s build from here GitHub 目录 一、创建远程仓库 二、远程库操作 2.1 查看远程库别名 2.2 创建远程仓库别名 2.3 推送本地分支到远程仓库 2.4 拉取远程库到本地库 2.5 克隆远程库到本地 三、 跨团队协作 3.1 团队内协作 3.2 跨团队协作 四、SSH免密码登…

Go : golang发布三方包流程简介

文章目录一、创建项目仓库二、拉去仓库&#xff0c;编辑代码三、推送与发布代码四、使用发布的第三方包小结一、创建项目仓库 1.输入仓库的名字&#xff0c;我这里输入simpleExample&#xff0c;用来做演示 2.选择public&#xff0c;公开。要不并不好拉 3.选择需要添加的文件(…

用PyPy加速Python程序

用PyPy加速Python程序 在《Python性能优化指南–让你的Python代码快x3倍的秘诀》中有提到&#xff0c;我们可以用更好的Python运行环境或运行时优化来提升Python的速度&#xff0c;其中最成熟、使用最简单的当属PyPy。用PyPy&#xff0c;可以在不改变源代码的情况下&#xff…

二叉树相关OJ - C++

文章目录&#xff1a;根据二叉树创建字符串二叉树的层序遍历二叉树的最近公共祖先二叉搜索树与双向链表从前序与中序遍历序列构造二叉树从中序与后序遍历序列构造二叉树二叉树的前序遍历&#xff08;非递归&#xff09;二叉树的中序遍历&#xff08;非递归&#xff09;二叉树的…

【LeetCode与《代码随想录》】数组篇:做题笔记与总结-Java版

代码随想录地址 是学习过程中的笔记&#xff01;图来自代码随想录。 文章目录理论题目704. 二分查找35. 搜索插入位置34. 在排序数组中查找元素的第一个和最后一个位置69. x 的平方根367.有效的完全平方数理论 数组是存放在连续内存空间上的相同类型数据的集合。 数组下标都是…

[附源码]java毕业设计新能源汽车租赁管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于armv8的kvm实现分析(一)虚拟化介绍

本文基于以下软硬件假定&#xff1a; 架构&#xff1a;AARCH64 内核版本&#xff1a;5.14.0-rc5 1 什么是虚拟化 虚拟化就是把一台物理计算机虚拟成多台逻辑计算机&#xff0c;每台逻辑计算机里面可以运行不同操作系统&#xff0c;而相互之间不受影响&#xff0c;其典型架构…

面试了个 985 毕业的同学,回答“性能调优”题时表情令我毕生难忘

又逢“金九银十”&#xff0c;年轻的毕业生们满怀希望与忐忑&#xff0c;去寻找、竞争一个工作机会。已经在职的开发同学&#xff0c;也想通过社会招聘或者内推的时机争取到更好的待遇、更大的平台。 然而&#xff0c;面试人群众多&#xff0c;技术市场却相对冷淡&#xff0c;面…

JavaIO流:概述

在接触 IO 流前&#xff0c;无论是 变量的声明、数组的创建&#xff0c;又或者是复杂的并发设计还是 Jvm 的性能调优&#xff0c;我们更多的还是和内存打交道。但我们知道计算机组成包括运算器&#xff0c;控制器&#xff0c;存储器&#xff0c;输入设备&#xff0c;输出设备。…

springcloud4:服务注册中心Eureka

直接调用即可&#xff0c;为什么用Eureka什么是服务治理&#xff1f; 多个服务调用&#xff0c;需要有依赖中心管理什么是服务注册&#xff1f; 有一个注册中心&#xff0c;当服务器启动时&#xff0c;会把自己的信息注册到注册中心上什么是服务发现&#xff1f; Client通过注册…

electron打包ffi-napi报错 npm ERR! gyp reason: read ECONNRESET

问题描述 这个问题用了我两天的时间&#xff0c;所以记录一下。 我们项目是使用electronvue&#xff0c;做支付功能的时候需要使用到ffi-napi依赖包。 最后打包的时候ffi-napi报错了&#xff0c;在package.json中去掉ffi-napi就可以打包&#xff0c;但是打包运行后提示缺少ff…

re:Invent 2022,探秘亚马逊云科技的重量级计算创新——Nitro

诞生于16年前的亚马逊云科技&#xff0c;开创了一个全新的云计算领域。秉持着创新与探索精神&#xff0c;自2012年开始&#xff0c;在每年一度的re:Invent全球大会上&#xff0c;亚马逊云科技都会发布最新的云计算技术。对IT产业演进产生了革命性的影响&#xff0c;Nitro系统就…

Java笔记(工厂模式、动态代理、XML)

一、工厂模式 软件设计模式&#xff08;Design pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的复用性。 什么…

代码随想录算法训练营第四十三天| LeetCode1049. 最后一块石头的重量 II、LeetCode494. 目标和、LeetCode474. 一和零

一、LeetCode1049. 最后一块石头的重量 II 1&#xff1a;题目描述&#xff08;1049. 最后一块石头的重量 II&#xff09; 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将…

让你快速高效的掌握linux内核编译过程

Linux内核编译 一、linux内核的配置与编译&#xff1a; 1.配置内核 1)导入默认配置&#xff1a; make xxxx_defconfig 注1&#xff1a;xxxx表示内核支持的芯片的名称 比如make exynos_defconfig 注2&#xff1a;内核源码中对每个支持的芯片都有默认的配置&#xff0c;默认配置很…

【博学谷学习记录】超强总结,用心分享|架构师-RabbitMQ消息可靠性保障

文章目录一、生产者保证1.1 失败通知1.2 发送方确认1.3 Broker丢失消息二、消费方消息可靠性2.1 消费者手动确认消息依靠三个对象&#xff1a;生产者、消费者、broker一、生产者保证 生产者发送消息到broker时&#xff0c;要保证消息的可靠性&#xff0c;主要的方案有&#xf…

5.28 综合案例2.0-简易起夜灯

HaaS506 - 简易起夜灯简介准备硬件连接图功能实现1.继电器使用说明2. 5.8G雷达感应传感器模块说明3.简易代码3.1测试log简介 案例为了解决晚上起床找不到灯的问题。当你从床上起来时&#xff0c;雷达感应传感器检测到你的活动后自动打开电灯。省去了寻找电灯开关的麻烦。 准备…

java学习笔记 day07-Java基础-综合练习

练习一&#xff1a;飞机票 需求: ​ 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。 ​ 按照如下规则计算机票价格&#xff1a;旺季&#xff08;5-10月&#xff09;头等舱9折&#xff0c;经济舱8.5折&#xff0c;淡季&#xff08;11月到来…

表白墙(web版)

文章目录前言一、需求分析1.表白墙页面设计2.表白墙功能二、实现1.客户端2.服务器端3.连接数据库前言 前面前端部分写过一个表白墙页面&#xff0c;但是它不能存储提交信息&#xff0c;为了能够让它在提交信息后可以保存其信息&#xff0c;页面刷新后信息依然存在&#xff0c;…

itk配准整理(1)

示例地址&#xff1a; itk\ITK\Examples\RegistrationITKv4\ImageRegistration7.cxx 说明&#xff1a;itk二维图像的配准&#xff1a;平移旋转缩放 效果图&#xff1a; 运行结果&#xff1a; 52 53.6213 [0.8333298229719548, -0.17450270771316403, -12.806452097490313, -1…