从零开始学习管道:进程通信的概念,特点和示例

news2024/12/26 23:07:11

在这里插入图片描述

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容通过进程通信的概念,引入管道,实操了管道的五种特性和四种场景,以及对应的管道的特点最后我们写了一个例子让我们对于管道,重定向等的只是更加的印象深刻

文章目录

    • 1.进程通信的介绍
      • 1.1进程通信目的
      • 1.2进程间通信发展
      • 1.3进程通信的分类
    • 2.管道
      • 2.1什么是管道
      • 2.2实操一下见见管道
      • 2.3管道的原理
      • 2.4直接编写样例代码
      • 2.5做实验,推导出管道
        • a.五种特性
        • b.四种场景:
      • 2.6管道的特点
      • 2.7添加一点设计,来完成一个基本的多进程控制代码

1.进程通信的介绍

1.1进程通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

1.2进程间通信发展

  • 管道

  • System V进程间通信

    System V 是一种基于本地版的进程通信,它是不能跨网络的,因为它只能本主机的内部进行进程间的通信,所以这也是它为什么会现在被边缘化的原因。关于System V我们只需要了解一个共享内存即可.

  • POSIX进程间通信

    POSIX 是一种基于网络版的进程通信。

    System V 和 POSIX相当于是进程间的通信的两套标准。

1.3进程通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

①首先进程是具有独立性的—无疑增加了通信的成本

②要让两个不同的进程通信,进行通信,前提条件是:先让两个进程,看到同一份“资源”。(进程的本质和前提)

③任何进程通信手段都需要遵循如下的原则解决进程间的通信问题:

我们在操作系统内创建一份公共的资源,例如一段缓冲区,它既不属于进程A,也不属于进程B,那么我们这一份资源既可以被进程A看到,也可以被进程B看到。所以我们可以把进程A产生的数据放到缓冲区中,然后进程B就可以从缓冲区中拿到这部分数据从而完成了进程间的通信!

综上:

​ a.想办法,先让不同的进程,看到同一份资源。

​ b.让一方写入,一方读取,完成通信过程,至于,通信目的与后续工作需要结合具体场景。

2.管道

2.1什么是管道

  • 管道是Unix中最古老的进程间通信的形式
  • 我们把从一个进程连接到另一个进程的一个数据流成为一个“管道”

2.2实操一下见见管道

who命令:查看当前Linux系统中有用户登录信息

image-20231123212353868

who | wc -l其中的wc -l表示统计输出结果的行数然后输出,那么整体的运行结果就表示的是当前Linux系统中有多少个用户登录

image-20231123212546537

再看一个例子

 //其中的&代表让当前的命令在后台执行
 [mi@lavm-5wklnbmaja lesson12]$ sleep 10000 | sleep 20000 | sleep 30000 &
[1] 24952
//我们可以查看到三个sleep进程,那么这三个进程被称为我们的兄弟进程,父进程都是我们的bash
[mi@lavm-5wklnbmaja lesson12]$ ps axj | head -1 && ps axj | grep sleep
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
22692 24950 24950 22692 pts/0    24995 S     1000   0:00 sleep 10000
22692 24951 24950 22692 pts/0    24995 S     1000   0:00 sleep 20000
22692 24952 24950 22692 pts/0    24995 S     1000   0:00 sleep 30000
22692 24996 24995 22692 pts/0    24995 S+    1000   0:00 grep --color=auto sleep

那么当我们使用管道执行命令的时候他是帮我们直接创建了这几个进程:比如上面例子中的sleep 10000 | sleep 20000 | sleep 30000who | wc -l都是如此,那么它是如何实现进程之间的数据进行通信的?

以上为例,首先管道也是文件,所以我们对应的who进程以写的形式打开管道,并且将自己的标准输出重定向管道

对应的wc -l进程就以读的形式打开管道,并且将自己的标准输入重定向到管道。

image-20231123213537515

2.3管道的原理

管道就是一个操作系统提供的纯内存级文件!

①父进程曾经打开的文件是不需要复制给子进程的,原因是子进程拷贝了一份父进程的struct files_struct,其中包含了父进程文件的描述符对应的数组,子进程可以通过这个数组依旧指向父进程的原来打开的文件,而不需要再打开一份造成资源的浪费。

②所以创建子进程的时候,fork子进程,只会复制进程相关的数据结构对象,不会复制父进程曾经打开的文件对象!

现象:这就是为什么fork之后,父子进程都printf,cout,都会向同一个显示器终端打印数据的原因!

因此我们的子进程也可以看到父进程创建的管道文件!完成了进程间通信的前提,让不同的进程看到了同一份资源!

这种管道只支持单向通信!!

③确定数据流向,关闭不需要的fd

所以我们在子进程关闭对应的读端,和父进程对应的写端,所以我们就可以通过管道将子进程的数据流向父进程。就可以进行正常的进程间的通信!

那么管道为什么是单向的?

原因是管道是基于文件进行通信的,文件读写缓冲区是分开的,当这种通信技术被发明出来的时候,我们发现进程的通信只能是单向的。首先作为父进程它打开管道是需要以读写方式打开,创建子进程之后才能关闭对应的读或者写,要不然子进程继承不到对应的读写方式打开文件,就不能进行一个写一个读了!

管道原理图

2.4直接编写样例代码

这里我们需要用到pipe接口:创建管道文件

其中它的参数int pipefd[2]被称为输出型参数,pipe接口将对应的读和写文件描述符写到这个数组里。

成功返回0,否则返回-1.

image-20231124150856768

#include<iostream>
#include<unistd.h>
#include<cerrno>
#include<assert.h>
#include<string.h>
#include<string>

using namespace std;

int main()
{

    //让不同的进程看到同一份资源!
    //任何一种任何一种进程间的通信中
    //,一定要 先 保证不同的进程之间看到同一份资源
    int pipefd[2] = { 0 };
    //1.创建管道
    int ret = pipe(pipefd);
    //创建失败
    if(ret < 0)
    {
        cout << "pipe error," << errno << ": " << strerror(errno) << endl;
        return 1;
    }
    //打印对应的pipefd
    cout << "pipefd[0]: " << pipefd[0] << endl;  //读端
    cout << "pipefd[1]: " << pipefd[1] << endl;  //写端

    //2.创建子进程
    pid_t id = fork();
    assert(id != -1); //正常应该用判断
    //意料之外用if,意料之内用assert

    if(id == 0)
    {
        //子进程
        //关闭对应的读端
        close(pipefd[0]);
        
        //4.开始通信---结合场景
        const string msg = "hello, 我是子进程";
        int cnt = 0;
        char buffer[1024];
        while(true)
        {
            snprintf(buffer, sizeof(buffer), "%s, 计数器:%d, 我的PID: %d", msg.c_str(), cnt, getpid());
            write(pipefd[1], buffer, strlen(buffer));
            sleep(1);
            cnt++;
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3.关闭不需要的文件描述符,父读子写,关闭对应的写端
    close(pipefd[1]);
    //4.开始通信---结合场景

    char buffer[1024];
    while(true)
    {
        //sleep(1);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            cout << "我是父进程,我收到了子进程发给我的消息:" << buffer << endl;
        }

    }
    close(pipefd[0]);
    
    
    return 0;
}

image-20231124211108964

2.5做实验,推导出管道

a.五种特性

​ 1.单向通信

​ 2.管道的本质是文件,因为fd的声明周期随进程,管道的声明周期是随进程的

​ 3.管道通信,通常用来进行具有“血缘”关系的进程,进行进程间通信。常用于父子通信—pipe打开管道,并不清楚管道的名字,匿名管道。

​ 4.在管道通信中,写入的次数,和读取的次数,不是严格匹配的,读写次数的多少没有强相关—表现—字节流

​ 5. 具有一定的协同能力,让reader和writer能够按照一定的步骤进行通信—自带同步机制

b.四种场景:

1.如果我们read读取完毕了所有的管道数据,如果对方不发,我就只能等待

2.如果我们writer端管道写满了,我们还能写吗?不能!

我们每次写入四个字节,写入了65535次管道就满了!

snprintf(buffer, 4, "s");

image-20231124211820571

3.如果关闭了写端,读取完毕,在读,read就返回0,表明读到了文件结尾。

image-20231124191750420

4.写端一直写,读端关闭,会发生什么?没有意义!OS不会维护无意义,低效率,或者浪费资源的事情,OS会杀死一直在写入的进程!OS会通过信号来终止进程,13)SIGPIPE

我们收一条指令之后五秒之后关闭父进程对应的读端,发现子进程也退出了!

image-20231124214343431

2.6管道的特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  • 管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

2.7添加一点设计,来完成一个基本的多进程控制代码

需求:父进程有多个子进程,父进程和这些子进程之间都有管道,父进程可以通过向子进程写入特定的消息,唤醒子进程,甚至让子进程执行某种任务。

首先搭建大体的框架:

#include<iostream>
#include<string>
#include<unistd.h>
#include<assert.h>
#include<vector>



using namespace std;

const int gnum = 5; //表示子进程数

int main()
{

    //1.先进行构建控制结构,父进程写,子进程读
    for(int i = 0; i < gnum; ++i)
    {
        //1.1创建管道
        int pipefd[2] = {0};
        int ret = pipe(pipefd);
        assert(ret == 0); //0正常 -1不正常
        (void)ret;

        //1.2创建进程
        pid_t id = fork();
        assert(id != -1);
        
        if(id == 0)
        {
            //子进程
            //1.关闭对应的fd,也就是写
            close(pipefd[1]);




            close(pipefd[0]);
            exit(0);
        }

        //父进程
        //1.3关闭不要的fd
        close(pipefd[0]);


    }


    return 0;
}

但是我们应该理解到,父进程有这么多的子进程,我们父进程在操作的时候,怎么分的清对应的子进程是哪一个?所以这时候我们应该将这些子进程组织起来,这就要利用到我们操作系统内部一直在践行的:先描述再组织

创建一个对应的结构体,然后用vector将其组织起来

//先描述
class EndPoint
{
public:
    pid_t _child; //子进程pid
    int _write_fd;//对应的文件描述符

public:
    //构造
    EndPoint(pid_t id, int fd) 
    :_child(id)
    , _write_fd(fd)
    {}
    //析构
    ~EndPoint()
    {}
};

    //在组织
    vector<EndPoint> end_points;

然后我们将以上的步骤封装成一个函数:

void creatProcesses(vector<EndPoint>& end_points)
{
    //1.先进行构建控制结构,父进程写,子进程读
    for(int i = 0; i < gnum; ++i)
    {
        //1.1创建管道
        int pipefd[2] = {0};
        int ret = pipe(pipefd);
        assert(ret == 0); //0正常 -1不正常
        (void)ret;

        //1.2创建进程
        pid_t id = fork();
        assert(id != -1);
        
        if(id == 0)
        {
            //子进程
            //1.3关闭不要的fd
            close(pipefd[1]);
            //我们期望,所有的子进程读取“指令”的时候,都从标准输入读取
            
            //1.3.1所以我们进行输入重定向
            dup2(pipefd[0], 0);

            //1.3.2子进程开始等待获取命令
            WaitCommend();


            close(pipefd[0]);
            exit(0);
        }

        //父进程
        //1.3关闭不要的fd
        close(pipefd[0]);


        //1.4将新的子进程和他的管道写端构建对象。
        end_points.push_back(EndPoint(id, pipefd[1]));
    }
}

主函数这样写:先让程序跑起来

int main()
{
    //在组织
    vector<EndPoint> end_points;

    creatProcesses(end_points);
    
    //2.那么我们这里就可以得到了五个子进程的id和对应的写端
    while(true)
    {
        sleep(1);
    }

    return 0;
}

我们运行了之后用ps去监视查看进程确实生成了我们对应了五个子进程:

image-20231124231658337

设计 WaitCommend();函数

这个函数就是让子进程去一直去读取管道中信息,读取到之后执行对应的任务。

void WaitCommend()
{
    while (true)
    {
        int command;
        int n = read(0, &command, sizeof(int));
        if (n == sizeof(int)) // 读取成功
        {
            t.Execute(command);
        }
        else if (n == 0) // 表示链接已经关闭
        {
            // 则不需要去读了
            break;
        }
        else // 读取错误
        {
            break;
        }
    }
}

主函数内部父进程调度发配任务:

主要分为三步:

  • 确定任务
  • 确定执行任务的子进程
  • 执行任务
int main()
{
    // 在组织
    vector<EndPoint> end_points;

    creatProcesses(end_points);

    // 2.那么我们这里就可以得到了五个子进程的id和对应的写端
    while (true)
    {
        //1.确定任务
        int command = COMMAND_LOG;

        //2.确定执行任务的子进程
        int child = rand() % end_points.size();

        //3.执行任务
        write(end_points[child]._write_fd, &command, sizeof(command));

        sleep(1);
    }

    return 0;
}

运行结果:

image-20231125121913303

所有源码:

Task.hpp

#pragma once

#include<iostream>
#include<vector>
#include<unistd.h>

using namespace std;


typedef void (*fun_t)(); //函数指针

//任务对应的操作码,约定每一个command是四个字节
#define COMMAND_LOG 0
#define COMMAND_MYSQL 1
#define COMMAND_REQUEST 2


//三个任务
void PrintLog()
{
    cout <<"进程的PID:" <<getpid()<< "打印日志任务,正在被执行" << endl;
}

void InsertMySQL()
{
    cout << "执行数据库任务,正在被执行" << endl;
}

void NetRequest()
{
    cout << "执行网络请求任务,正在被执行" << endl;
}



class Task
{
public:
    Task()
    {
        funcs.push_back(PrintLog);
        funcs.push_back(InsertMySQL);
        funcs.push_back(NetRequest);
    }

    //任务执行函数
    void Execute(int command)
    {
        if(command >= 0 && command < funcs.size()) funcs[command]();
    }

    ~Task()
    {}

public:
    vector<fun_t> funcs;

};



myprocess.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <assert.h>
#include <vector>

#include "Task.hpp"

using namespace std;

const int gnum = 5; // 表示子进程数

// 定义对应的任务对象
Task t;

// 先描述
class EndPoint
{
public:
    pid_t _child;  // 子进程pid
    int _write_fd; // 对应的文件描述符

public:
    // 构造
    EndPoint(pid_t id, int fd)
        : _child(id), _write_fd(fd)
    {
    }
    // 析构
    ~EndPoint()
    {
    }
};

void WaitCommend()
{
    while (true)
    {
        int command;
        int n = read(0, &command, sizeof(int));
        if (n == sizeof(int)) // 读取成功
        {
            t.Execute(command);
        }
        else if (n == 0) // 表示链接已经关闭
        {
            // 则不需要去读了
            break;
        }
        else // 读取错误
        {
            break;
        }
    }
}

void creatProcesses(vector<EndPoint> &end_points)
{
    // 1.先进行构建控制结构,父进程写,子进程读
    for (int i = 0; i < gnum; ++i)
    {
        // 1.1创建管道
        int pipefd[2] = {0};
        int ret = pipe(pipefd);
        assert(ret == 0); // 0正常 -1不正常
        (void)ret;

        // 1.2创建进程
        pid_t id = fork();
        assert(id != -1);

        if (id == 0)
        {
            // 子进程
            // 1.3关闭不要的fd
            close(pipefd[1]);
            // 我们期望,所有的子进程读取“指令”的时候,都从标准输入读取

            // 1.3.1所以我们进行输入重定向
            dup2(pipefd[0], 0);

            // 1.3.2子进程开始等待获取命令
            WaitCommend();

            close(pipefd[0]);
            exit(0);
        }

        // 父进程
        // 1.3关闭不要的fd
        close(pipefd[0]);

        // 1.4将新的子进程和他的管道写端构建对象。
        end_points.push_back(EndPoint(id, pipefd[1]));
    }
}
int main()
{
    // 在组织
    vector<EndPoint> end_points;

    creatProcesses(end_points);

    // 2.那么我们这里就可以得到了五个子进程的id和对应的写端
    while (true)
    {
        //1.确定任务
        int command = COMMAND_LOG;

        //2.确定执行任务的子进程
        int child = rand() % end_points.size();

        //3.执行任务
        write(end_points[child]._write_fd, &command, sizeof(command));

        sleep(1);
    }

    return 0;
}

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

pwn:[NISACTF 2022]ReorPwn?

题目 按正常方式走&#xff0c;发现指令被反着输出

C++ Boost 异步网络编程基础

Boost库为C提供了强大的支持&#xff0c;尤其在多线程和网络编程方面。其中&#xff0c;Boost.Asio库是一个基于前摄器设计模式的库&#xff0c;用于实现高并发和网络相关的开发。Boost.Asio核心类是io_service&#xff0c;它相当于前摄模式下的Proactor角色。所有的IO操作都需…

通过互联网代理部署Docker+Kubernetes 1.28.1

一、背景 在公司环境中&#xff0c;我们往往都是无法直接连接外网的&#xff0c;之前写过一篇文章&#xff0c;是通过外网自建的中转机器下载需要的离线包&#xff0c;并在内网搭建一个harbor&#xff0c;通过harbor的方式搭建了一个kubernetes&#xff0c;但是这种方式还是有…

小程序姓名:ssm+vue基本微信小程序的个人健康管理系统

项目介绍 首先,论文一开始便是清楚的论述了小程序的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了小程序的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数…

2023 年 认证杯 小美赛 ABC题 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在认证杯 小美赛前为大家提供了许多资料的内容呀&am…

openEuler20.03学习01-创建虚拟机

赶个时髦&#xff0c;开始学习openEuler 20.03 (LTS-SP3) 操作系统iso下载地址&#xff1a;https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/ISO/x86_64/openEuler-20.03-LTS-SP3-x86_64-dvd.iso 公司有现成的vmware环境&#xff0c;创建虚拟机i测试&#xff0c…

检索增强生成架构详解【RAG】

生成式AI技术很强大&#xff0c;但它们受到知识的限制。 虽然像 ChatGPT 这样的LLM可以执行许多任务&#xff0c;但每个LLM的基线知识都存在基于其训练数据的差距。 如果你要求LLM写一些关于最近趋势或事件的文章&#xff0c;LLM不会知道你在说什么&#xff0c;而且回答最好是混…

vivado产生报告阅读分析22

“ Advanced ”选项卡 “ Advanced ” &#xff08; 高级 &#xff09; 选项卡如下图所示。 在“ Advanced ”选项卡中提供了以下字段 &#xff1a; • “ Report ” &#xff08; 报告 &#xff09;&#xff1a; 选中“ Advanced ”选项卡中的“ Cells to Analyze ” &…

GEE:通过将 Landsat 5、7、8、9 的 C02 数据集合并起来,构建 NDVI 长时间序列

作者:CSDN @ _养乐多_ 本文记录了在 Google Earth Engine(GEE)平台上,将 Landsat-5、Landsat-7、Landsat-8 和 Landsat-9 的数据合成为一个影像集合,并生成 NDVI(归一化植被指数)的时间序列的代码。 代码封装成了函数,方便调用,结果如下图所示, 在实际应用中,可能…

地表最强端口扫描神器之namp工具的使用方法

namp使用方法 文章目录 namp使用方法nmap工具是什么?nmap工具是用来干什么的?nmap扫描工具如何使用-A-sS-sP-p-sV nmap工具是什么? nmap是一款强大的网络扫描和安全审计工具&#xff0c;它可以帮助用户探测网络中存活的主机&#xff0c;扫描开放的端口&#xff0c;检测运行…

c++_继承

&#x1f3f7;如被何实现一个不能被继承的类&#xff08;或是继承无意义的类&#xff09; 将构造函数定义成私有的就行了&#xff0c;即&#xff1a;私有化父类的构造函数 c 11 新增关键字final 修饰父类直接不能被继承 class A final {........ }&#x1f3f7;继承与有元 有…

基于mediapipe的人手21点姿态检测模型—CPU上检测速度惊人

前期的文章,我们介绍了MediaPipe对象检测与对象分类任务,也分享了MediaPipe的人手手势识别。在进行人手手势识别前,MediaPipe首先需要进行人手的检测与人手坐标点的检测,经过以上的检测后,才能把人手的坐标点与手势结合起来,进行相关的手势识别。 MediaPipe人手坐标点检测…

Linux学习笔记之六(进程之间的管道通信和信号处理)

目录 1、管道通信1.1、无名管道1.1、有名管道 2、信号处理2.1、信号的种类和发送2.2、信号的接受和处理 1、管道通信 管道通信是一个设备中进程与进程之间通信的一种方式&#xff0c;分为无名管道和有名管道两种。前者只能用于有亲缘关系的进程之间的通信&#xff0c;如父子进…

B/S前后端分离的Java医院云HIS信息管理系统源码(LIS源码+电子病历源码)

HIS系统采用主流成熟技术开发&#xff0c;软件结构简洁、代码规范易阅读&#xff0c;SaaS应用&#xff0c;全浏览器访问前后端分离&#xff0c;多服务协同&#xff0c;服务可拆分&#xff0c;功能易扩展。多医院、多集团统一登录患者主索引建立、主数据管理&#xff0c;统一对外…

windows cmd执行远程长脚本

背景 有时候我们想在未进行一些环境设置&#xff0c;或者工具使用者电脑中执行一段初始化脚本&#xff0c;为了简化使用者的理解成本&#xff0c;通常给使用者一段代码执行初始化电脑中的设置&#xff0c;尤其是这段初始化脚本比较长的时候。 脚本制作者 比如将需要执行的命…

【React】Memo

组件重新渲染时缓存计算的结果。 实例&#xff1a;count1计算斐波那契数列&#xff0c;count2和count1可以触发数值变化。使用memo可以使只有在count1变化时触发斐波那契数列计算函数&#xff0c;而count2变化时不触发斐波那契数列计算函数。 import { useMemo } from "r…

【Ambari】HDFS基于Ambari的常规运维

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f…

Vue3的计算属性(computed)和监听器(watch)案例语法

一&#xff1a;前言 Vue3 是 Vue2 的一个升级版&#xff0c;随着 2023年12月31日起 Vue2 停止维护。这意味着 Vue3 将会为未来国内一段时间里&#xff0c;前端的开发主流。因此熟练的掌握好 Vue3 是前端开发程序员所不可避免的一门技术栈。而 Vue3 是 Vue2 的一个升级版&#x…

画中画视频剪辑:如何实现多画面融合,提升创作质量

在视频剪辑的过程中&#xff0c;画中画是一种常见的技巧&#xff0c;它能够将多个画面融合在一起&#xff0c;创造出一种独特的效果&#xff0c;增强视频的观赏性和表现力。这种技巧常常用于电影、电视和广告中&#xff0c;以增加视觉冲击力&#xff0c;引导注意力&#xff0c;…

Vue3设计思想及响应式源码剖析 | 京东物流技术团队

一、Vue3结构分析 1、Vue2与Vue3的对比 对TypeScript支持不友好&#xff08;所有属性都放在了this对象上&#xff0c;难以推倒组件的数据类型&#xff09;大量的API挂载在Vue对象的原型上&#xff0c;难以实现TreeShaking。架构层面对跨平台dom渲染开发支持不友好,vue3允许自…