『 Linux 』 进程间通信 - 匿名管道 (万字)

news2025/1/15 6:43:14

文章目录

    • 什么是管道
    • 匿名管道的直接原理
    • pipe( )系统调用接口
    • 匿名管道代码示例
    • 匿名管道的特征
    • 总结


什么是管道

请添加图片描述

管道(Pipe) 是一种基本的进程间通信(IPC)机制,允许一个进程与另一个进程之间进行数据传输;

管道工作方式类似于生活中的水管因此命名为管道,数据从一端流入另一段流出,数据流为单向;

Linux中可以使用who | wc -l查看当前登入系统的用户数;

who命令用与显示当前登入系统的用户信息,其中一条会话代表一个用户;

wc -l命令统计当前行数;

两条命令通过管道符|进行连接,即将显示的信息通过管道符传输给wc命令再进行统计行数;

|符号即为一种管道;

管道存在两种基本类型:

  • 匿名管道(Anonymous Pipes)

    不存在命名的管道,用于有亲缘关系的进程之间的通信(如父子进程或兄弟进程等);

    匿名管道通常用于单个系统内部的进程通信;

  • 命名管道(Named Pipes)

    也称为FIFO(First In First Out),拥有命名并存在于文件系统中;

    命名管道允许没有亲缘关系的进程之间进行通信;


匿名管道的直接原理

请添加图片描述

每个进程需要维护其task_struct结构体,对应的内核数据结构中存在一个struct file_struct*指针指向一个file_struct结构体,这个结构体中存在一个struct file* fd_array[]指针数组,数组的下标为文件描述符;

对应的struct file结构体存放打开的文件的基本信息;

这些信息包括但不限于:

  • Inode

    文件的Inode编号;

  • file_operators

    提供给上层的读写接口方法集;

  • 缓冲区

    对于普通文件而言这个缓冲区通常为 页缓冲区 ;

    缓冲区与文件系统配合实现数据的写入与读取;

匿名管道是一种区别于普通文件的内存级文件;

不存在于磁盘中不基于文件系统,操作系统不会为匿名管道文件分配Inode与对应的数据块;

对应的读写方法file_operators是针对于缓冲区的读写;

当进程创建子进程时子进程为父进程的一个拷贝;

操作系统会为子进程单独维护一个task_struct结构体以及其对应的内容包括file_struct结构体与文件描述符表;

文件系统与进程管理之间为并列关系,文件不会因为创建子进程而单独为其拷贝一份新文件;

子进程文件描述符对应的结构体指针所指向的文件与父进程相同;

进程间通信的本质是 “让不同的进程看到同一份资源” ;

在创建子进程时即可实现两个不同的进程看到同一份"资源",即管道文件的缓冲区;

在打开文件时通常会记录打开文件的方式 (读/写),在创建子进程时打开方式也会连同一起拷贝,这意味着单独以读或是写的方式打开文件不能使得两个不同进程进行通信;

在进行匿名管道通信时进程将占用两个文件描述符分别以读和写的方式打开管道文件,在创建子进程后根据需求关闭另一个文件描述符从而实现单向通信;

系统并不会在使用管道时为用户关闭某个文件描述符,该操作由用户自行决定;

为确保管道的正常使用与进程间通信,用户需要手动关闭不需要的文件描述符;

单个管道只能进行单向通信,若是使单个管道进行双向通信可能会因为读写位置不同或数据覆盖,数据碎片等问题造成通信错误;

需要利用管道进行双向通信时可采用两个管道的方式,其中每个管道负责一个方向的通信;


pipe( )系统调用接口

请添加图片描述

使用 open()等系统调用接口创建的文件是一种磁盘级文件,在文件系统中存在自身的文件名,Inode与数据块且将被文件系统管理;

管道是一种内存级文件,不存在对应的文件名与Inode,数据块分配,内存级文件不被文件系统所管理,不可使用open()等接口函数创建;

匿名管道文件的创建需要通过系统调用接口pipe()进行创建;

PIPE(2)                               Linux Programmer's Manual                               PIPE(2)

NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
  • 头文件

    pipe()系统调用接口存在于<unistd.h>头文件中;

  • 参数

    int pipefd[2]参数代表使用该系统调用接口时需要传递一个数组,数组中只需要包含两个int类型的元素;

    该参数是一种输出型参数;

    • 传入型参数

      这类参数用于向函数提供需要的数据或信息,函数通过这些参数读取传入的值,但不会修改它们;

    • 输出型参数

      输出参数则用于从函数内部向外部返回额外的数据,函数通过修改这些参数的值来传递数据给调用者;

    pipefd[0]pipefd[1]分别代表创建的管道文件对应的文件描述符,其中 0号下标对应的int类型数据代表读,1号下标代表写;

  • 返回值

    函数调用成功时返回0;

    函数调用失败时返回-1并设置errno;

匿名管道的使用方式一般为:

  • 父进程调用pipe()系统调用接口创建管道文件

  • 父进程调用fork()系统调用接口创建子进程(或间接创建具有亲缘关系的进程)

  • 判断数据流方向( 父流向子/子流向父 )

  • 根据需求(数据流向)关闭父子进程的另一个文件描述符

  • 进行通信

    通信一般采用write()系统调用接口与read()系统调用接口;

  • 关闭剩余文件描述符

    在通信过后可以根据条件关闭通信过的文件描述符,也可不关闭(最终管道文件将会自动被操作系统回收);


匿名管道代码示例

请添加图片描述

  • 头文件与宏定义

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <string>
    
    #define N 2
    
    #define NUM 1024
    using namespace std;
    
    • Npipe()系统调用接口参数的数组大小
    • NUM为用户层缓冲区的大小
  • main函数

    int main() {
      int pipefd[N] = {0};
      int n = pipe(pipefd);
      if (n < 0) {
        cerr << "pipe error" << endl;
        exit(-1);
      }
    
      pid_t id = fork();
      if (id < 0) {
        cerr << "fork error" << endl;
        exit(-1);
      }
      /*
        child -> parent
        pipefd[0] -- 'r'
        pipefd[1] -- 'w'
      */
    
      if (id == 0) {
        // child - `w`
        close(pipefd[0]);
        // IPC code
        PIC::Write(pipefd[1]);
        exit(0);
      }
    
      // parent - `r`
      close(pipefd[1]);
      // IPC code
      PIC::Read(pipefd[0]);
      pid_t rid = waitpid(id, nullptr, 0);
      if (rid < 0) return 3;
    
      return 0;
    }
    

    该示例为子进程向父进程进行单向通信;

    设置int类型数组pipefd[N]并调用pipe()系统调用接口创建匿名管道文件;

    利用fork()系统调用接口创建子进程并根据需求调用close()系统调用接口关闭父子进程中不需要的文件描述符;

    父进程调用waitpid()系统调用接口进行进程等待;

    Read()Write()接口用来完成具体的通信过程;

    子进程调用Write(),父进程调用Read()完成通信;

  • Read()Write()实现

    namespace PIC {
    
    void Write(int wfd) {
      char buff[NUM];
      pid_t self = getpid();
      string s = "I am a child";
      int number = 0;
      while (true) {
        buff[0] = 0;  // 字符串清空 (为读者展示该数组将被视为一个字符串)
    
        snprintf(buff, sizeof(buff), "%s-%d-%d", s.c_str(), self, number++);
        // cout << buff << endl;
    
        // 向父进程进行通信
        write(wfd, buff, strlen(buff));  // 进行写入时数据不作为一个字符串
        sleep(1);
      }
    }
    void Read(int rfd) {
      char buff[NUM];
      while (true) {
        buff[0] = 0;  // 字符串清空 (为读者展示该数组将被视为一个字符串)
        ssize_t n = read(rfd, buff, sizeof(buff));
        if (n > 0) {
          buff[n] = 0;  // 需要打印时将需要称为一个字符串 需要添加'\0'
          cout << "parent process get a massage [" << getpid() << "]# " << buff
               << endl;
        }
          //忽略read()调用失败
      }
    }
    
    }  // namespace PIC
    

    创建命名空间PIC避免出现命名冲突;

    • Write()

      创建用户层缓冲区buff[NUM]用于存储子进程需要向父进程写入的内容;

      调用snprintf()C标准接口用于将字符串格式化后写入用户层缓冲区buff[](snprintf()具体调用查看手册);

      调用write()系统调用接口将用户层缓冲区内容buff[]写入至内核缓冲区(匿名管道文件的缓冲区,写入过程中文件数据不当做字符串看待);

      调用sleep()使每向内核缓冲区写入一条数据后休眠1s;

    • Read()

      创建用户层缓冲区buff[NUM]用于存储接收的由子进程写给父进程的内容;

      调用read()系统调用接口从内核缓冲区(匿名管道文件的缓冲区)读取内容并写入至用户层缓冲区buff[]中并用n接收返回值;

      子进程在向父进程写入时将数据以字符串形式看待,此处需要打印需要将数据以字符串看待需要buff[n]处添加字符串结束符\0;

      利用std::cout打印接收的内容;

缓冲区为内核的空间,用户层必须通过系统调用接口才能间接对内核缓冲区进行操作;


匿名管道的特征

请添加图片描述

  • 匿名管道只能为具有血缘关系的进程间进行通信

    不具有血缘关系的进程无法利用匿名管道进行通信;

  • 管道只能单向通信

    单个管道进行双向通信将会因为读写位置不同或数据覆盖,数据碎片等问题造成通信错误;

  • 父子进程通信时会进行协同

    在上段代码中子进程向父进程写入数据时利用sleep()每隔1s进行一次写入;

    父进程在读取子进程数据时不进行休眠;

    运行上段代码并在另一个会话窗口利用脚本观察父子进程情况;

    $ while :; 
    do ps axj | head -1 && ps axj | grep mytest | grep -v grep ; 
    echo"#####################" ; 
    sleep 1 ;
    done
    

    脚本结果如下:

    #####################
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    29927 30127 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    30127 30128 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    #####################
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    29927 30127 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    30127 30128 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    

    观察结果父子进程都进行了休眠;

    当子进程向父进程传输数据时父进程会直接接收;

    若是子进程未向父进程传输数据时为了避免读取"脏数据"(即错误或无效数据),父进程将会等待子进程进行下一次的通信;

    进程间通信的本质是 “使不同进程看到同一个资源” ,这意味着这份资源将被多个执行流共享;

    因此可能会出现 访问冲突临界资源竞争 等问题;

    父子进程间协同的方式一般采用 同步与互斥 ,主要保护管道文件资源的数据安全;

  • 匿名管道通信时的四种情况

    • 管道为空时读端将进行阻塞

      参考上文 父子进程通信时会进行协同 ;

    • 管道为满时写端将进行阻塞

      在原代码基础上取消子进程的sleep()并在父进程开头处sleep(5),结尾处sleep(100)进行等待(只进行一次读取,sleep(100)为与一次读取进行分割避免第二次read());

      在两个会话中分别运行程序与监控脚本;

      结果如下:

      • 监控脚本

        $ while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep ; echo "#####################" ; sleep 1 ;done
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        #####################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        29927 32142 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        32142 32143 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        #####################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        29927 32142 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        32142 32143 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        
        #...
        
        
      • 程序结果

        $ ./mytest 
        parent process get a massage [32142] # I am a child-32143-0I am a child-32143-1I am a child-32143-2I am a child-32143-3I am a child-32143-4I am a child-32143-5I am a child-32143-6I am a child-32143-7I am a child-32143-8I am a child-32143-9I am a child-32143-10I am a child-32143-11I am a child-32143-12I am a child-32143-13I am a child-32143-14I am a child-32143-15I am a child-32143-16I am a child-32143-17I am a child-32143-18I am a child-32143-19I am a child-32143-20I am a child-32143-21I am a child-32143-22I am a child-32143-23I am a child-32143-24I am a child-32143-25I am a child-32143-26I am a child-32143-27I am a child-32143-28I am a child-32143-29I am a child-32143-30I am a child-32143-31I am a child-32143-32I am a child-32143-33I am a child-32143-34I am a child-32143-35I am a child-32143-36I am a child-32143-37I am a child-32143-38I am a child-32143-39I am a child-32143-40I am a child-32143-41I am a child-32143-42I am a child-32143-43I am a child-32143-44I am a child-32143-45I am a child-32143-46I am a child-32143-47I am a child-32143-48I am 
        

      结果来看在运行程序时父进程因sleep(5)并未接收到子进程传输的数据;

      当休眠结束时将管道内的所有数据进行一次性读取;

      从读取的数据来看最终写入的数据停留在了I am a child-32143-48I am ;

      这意味着管道此时已经被写满了;

      当管道满了时写端将进行阻塞,等待读端读取数据后才能进行下一次写入;

    • 读端正常,写端关闭

      利用man查看read()系统调用接口返回值;

      RETURN VALUE
             On  success, the number of bytes read is returned (zero indicates end
             of file), and the file position is advanced by this  number.  
             ...
      

      读端正常写端关闭时当管道中的数据被读端读完后读端将会读取到0表示已经读到文件(管道Pipe)结尾且不会被阻塞;

      故为防止该种情况需要在父进程中进行特殊处理(上述原文件并未对该情况进行处理);

    • 写端正常,读端关闭

      当一个进程尝试向管道的写端写入数据,而管道的读端已经被所有相关进程关闭时,该进程会收到SIGPIPE信号;

      默认情况下SIGPIPE信号会终止该进程;

      这是因为如果没有任何进程能够从管道读取数据,继续写入数据就没有意义;

      操作系统通过发送SIGPIPE信号来通知这一点;

      修改上文原代码,使写端持续每隔一秒对管道文件缓冲区进行写入(原代码中保持不变);

      读端读取3次后退出,即读端被关闭:

      void Read(int rfd) {
        int cnt = 0;
        char buff[NUM];
        buff[0] = 0;
        while (true) {
          buff[0] = 0;  // 字符串清空 (为读者展示该数组将被视为一个字符串)
          ssize_t n = read(rfd, buff, sizeof(buff));
          if (n > 0) {
            buff[n] = 0;  // 需要打印时将需要称为一个字符串 需要添加'\0'
            cout << "parent process get a massage [" << getpid() << "]# " << buff
                 << endl;
          }
          if (cnt++ > 5) break;
        }
      }
      

      子进程若被信号杀死父进程可以看到其对应状态;

      修改原代码main函数中父进程的操作,使其在子进程结束后sleep(3),并调用宏WTERMSIG观察子进程被哪个信号杀死;

        // parent - `r`
        close(pipefd[1]);
        // IPC code
        PIC::Read(pipefd[0]);
        close(pipefd[0]);
        sleep(3);//观察僵尸状态
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        cout << "Parent process [" << getpid()
             << "]@ waited for child process sucessed " << endl
             << "Child process terminated by signal " << WTERMSIG(status) << endl;
      
        if (rid < 0) return 3;
      

      在两个会话中调用监控脚本与运行程序;

      结果如下:

      • 运行结果:

        $ ./mytest 
        parent process get a massage [2381]# I am a child-2382-0
        parent process get a massage [2381]# I am a child-2382-1
        parent process get a massage [2381]# I am a child-2382-2
        parent process [2381]@ waited for child process sucess 
         the status is 0  , Child process terminated by signal 13
        
      • 脚本结果

        #######################################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        31962  4575  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
         4575  4576  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
        #######################################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        31962  4575  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
         4575  4576  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
        #######################################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        31962  4575  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
         4575  4576  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
        #######################################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        31962  4575  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
         4575  4576  4575 31962 pts/0     4575 Z+    1002   0:00 [mytest] <defunct>
        #######################################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        31962  4575  4575 31962 pts/0     4575 S+    1002   0:00 ./mytest
         4575  4576  4575 31962 pts/0     4575 Z+    1002   0:00 [mytest] <defunct>
        

        读端(父进程)读取两次后关闭读端并sleep(3);

        读端被关闭时写端(子进程)立马为僵尸状态Z+;

        而后读端阻塞sleep(3)结束后两个进程退出;

        打印结果中子进程(写端)被13号信号杀死,kill -l命令查看信号集:

      $ kill -l
       1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
       6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
      11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
      16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
      ...
      

      13号信号对应信号SIGPIPE;

  • 匿名管道具有固定大小

    利用ulimit命令带-a选项查看操作系统的限制;

    $ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 7269
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 65535
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 4096
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    

    其中pipe size大小为512bytes * 84kb;

    修改原代码中Write()为:

    void Write(int wfd) {
      int number = 0;
      while (true) {
        char c = 'c';
        write(wfd, &c, 1);  
        number++;
        cout << number << endl;
      }
    }
    
    //读端对应进行阻塞(该测试中用不到读端)
    

    即每次向管道文件内核缓冲区中写入一个字节;

    重新编译运行代码结果为:

    ...
    65534
    65535
    65536
    ^C #进行阻塞时 Ctrl+C 停止继续执行
    

    最终结果为65536字节,约为64kbulimit中结果不符;

    • 原因是匿名管道的缓冲区大小是固定的,但这个大小由操作系统决定,不同的操作系统和配置可能会有不同的管道缓冲区大小;
  • 管道是面向字节流的

    参考上文 “管道的四种情况 > 管道为满时写端将进行阻塞” ,写端向读端进行数据传输时只按照字节进行写入;

    最终的格式将取决于读端采用什么方式对写端的内容进行接收;

    写端可以连续写入任意数量的字节,而读端可以根据需要读取任意数量的字节;

    故写端不需要考虑读端采用何种方式进行接收,读端不需要考虑写端采用何种方式进行写入;

  • 匿名管道是基于文件的

    匿名管道不属于文件系统(不存在文件名,Inode与对应的数据块),但匿名管道是基于文件的,其必须通过系统调用接口与文件描述符的配合才能进行使用;

    同时匿名管道的生命周期是由进程决定的(引用计数);


总结

请添加图片描述

  • 管道是一种基于文件描述符的进程间通信(IPC)机制,允许单向数据传输;

  • 有两种基本类型的管道

    匿名管道用于有亲缘关系的进程间通信;

    命名管道(FIFO) 允许没有亲缘关系的进程间通信;

  • 管道存在固定大小的缓冲区,由操作系统决定,面向字节流,支持连续字节的读写操作;

  • 匿名管道的生命周期与创建它的进程相关,不属于文件系统,但通过文件描述符进行操作;

  • 进程间通信通过管道需要正确管理文件描述符,如关闭不需要的端以避免阻塞和SIGPIPE信号;

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

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

相关文章

学习笔记——动态路由——RIP(距离矢量协议)

一、距离矢量协议 1、距离矢量协议 矢量行为&#xff1a;协议收到一个路由之后&#xff0c;查看是否可以加入到本地的路由表中&#xff0c;如果可以加入&#xff0c;则可以传递&#xff0c;如果不可以加入&#xff0c;则无法传递。 距离矢量路由协议 RIP基于距离矢量算法(又…

智慧语音助手学习

智慧语音助手 1 语音识别系统&#xff08;ASR&#xff09;2 语义理解1 传统的实现方法1. 音频信号处理和语音识别&#xff08;Automatic Speech Recognition, ASR&#xff09;2. 自然语言理解&#xff08;Natural Language Understanding, NLU&#xff09;3. 对话管理&#xff…

【高数强化】武忠祥高等数学辅导讲义+严选题错题整理

第一章 函数、极限和连续 高等数学辅导讲义 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 严选题 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 第二章 一元函数微分学 高等数学辅导讲义 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 严选…

港股再遭重挫,市场到底在恐慌什么?

恒指上周五挫逾300点后&#xff0c;今日早间延续低迷低开超百点&#xff0c;随后一路震荡下行&#xff0c;最低见17789.57点。整体市场都表现相当低迷&#xff0c;但17750附近有一定支撑。截至收盘&#xff0c;恒指收跌0.00%。 港股持续在万八关徘徊&#xff0c;国资委旗下国新…

8.12 矢量图层面要素单一符号使用六(SVG填充)

文章目录 前言SVG填充&#xff08;SVG fill&#xff09;QGis设置面符号为SVG填充&#xff08;SVG fill&#xff09;SVG填充&#xff08;SVG fill&#xff09; 总结 前言 本章介绍矢量图层线要素单一符号中使用SVG填充&#xff08;SVG fill&#xff09;的使用说明&#xff1a;文…

AI大模型之争:通用性与垂直性,哪个更具优势?

文章目录 每日一句正能量前言背景介绍能力分析通用大模型的能力&#xff1a;垂直大模型的能力&#xff1a;差异与互补性分析&#xff1a; 难点探究1. 算力挑战2. 数据挑战3. 算法挑战4. 泛化能力5. 可解释性和透明度6. 伦理和偏见问题7. 成本效益 后记 每日一句正能量 昨天已逝…

DIY灯光特效:霓虹灯动画制作教程

下面我们根据这张霓虹灯案例,教大家如何用智能动物霓虹灯闪烁的效果,大家可以根据思路,实现自己想要的动效效果,一起动手来做吧。 即时设计-可实时协作的专业 UI 设计工具 设置背景 新建画板尺寸为:800PX^600PX,设置背景色#120527。 绘制主题 输入自己喜欢文案,轮廓化,具体…

SaaS产品运营:如何将自助服务加入销售流程中【含案例】

在SaaS产品运营中&#xff0c;将自助服务有效融入销售流程&#xff0c;不仅可以提升用户体验&#xff0c;还能提高销售效率和客户满意度。以下是关于如何将自助服务加入销售流程中的具体建议&#xff1a; 一、明确自助服务的目标和内容 首先&#xff0c;需要明确自助服务的目…

​【VMware】VMware Workstation的安装

目录 &#x1f31e;1. VMware Workstation是什么 &#x1f31e;2. VMware Workstation的安装详情 &#x1f33c;2.1 VMware Workstation的安装 &#x1f33c;2.2 VMware Workstation的无限使用 &#x1f31e;1. VMware Workstation是什么 VMware Workstation是一款由VMwar…

光伏能源圈

2021年&#xff0c;新型电力系统之下&#xff0c;中国已形成了两网五大六小的电力新格局&#xff1a; 两大电网&#xff1a;国家电网、南方电网 五大电力央企&#xff1a;国家能源投资集团、中国华能集团、中国华电集团、中国大唐集团、国家电力投资集团。 六小豪门&#xf…

vue的学习之路(Axios 基本使用)

<script src"js/axios.min.js"></script> 3、在页面中引用 4、发送异步请求 url?xxx rest请求【GET,POST,DELETE,PATCH】 5、准备一个SpringBoot的控制层与axios进行交互 实体类 public class User { private Integer id; private String user…

Ollama在Windows11部署与使用QWen2模型

0&#xff0c;Ollama介绍 Ollama是一个开源项目&#xff0c;旨在让用户能够轻松地在其本地计算机上运行大型语言模型&#xff08;LLM&#xff09;&#xff0c;是一个开源的大型语言模型服务。它支持各种LLM&#xff0c;包括Llama 3、Mistral和Gemma。提供了类似OpenAI的API接口…

Java8使用Stream流实现List列表查询、统计、排序、分组、合并

Java8使用Stream流实现List列表查询、统计、排序以及分组 目录 一、查询方法1.1 forEach1.2 filter(T -> boolean)1.3 filterAny() 和 filterFirst()1.4 map(T -> R) 和 flatMap(T -> Stream)1.5 distinct()1.6 limit(long n) 和 skip(long n) 二、判断方法2.1 anyMa…

OS中断机制-外部中断触发

中断函数都定义在中断向量表中,外部中断通过中断跳转指令触发中断向量表中的中断服务函数,中断指令可以理解为由某个中断寄存器的状态切换触发的汇编指令,这个汇编指令就是中断跳转指令外部中断通过在初始化的时候使能对应的中断服务函数如何判断外部中断被触发的条件根据Da…

【C++进阶之路】C++11——正则表达式

序言 正则表达式&#xff0c;简而言之就是用来匹配指定模式字符串的工具&#xff0c;在计算机的世界中&#xff0c;它发挥着很大的作用&#xff0c;比如编译器的词法分析&#xff0c;注册时验证密码的复杂度&#xff0c;爬虫爬取固定格式的数据时等场景都要用到。那么本篇的目的…

超市库存管理系统

本系统主要利用C#和SQL建立系统,主要实现一下几个功能:登录、商品管理、供应商管理、库存管理、查询统计和系统管理几个模块。 1.数据库设计 总共有6个表,表名取得比较随意,专业人士开发最好用对应的英文。 用户表(sys_user) 用户表有3个字段:分别为用户名(userName…

如何将一个web端程序打包成一个pc端程序(exe文件)?

如何将一个Web端程序打包成一个PC端程序&#xff0c;例如一个可执行的EXE文件&#xff0c;是许多开发者常见的需求。下面将详细解释如何使用Nativefier工具将Web端程序打包成PC端程序的具体步骤。 目录 下载并安装Node.js验证Node.js和npm的安装安装Nativefier使用Nativefier打…

近2年时间,华为手机上的卫星通信功能发展成怎样了?

自从Mate 50 系列支持北斗卫星短报文功能以来&#xff0c;已经过去了近2年的时间&#xff0c;卫星相关的功能也从最开始的摸索、罕见&#xff0c;逐渐变得成熟、在各品牌旗舰机上常见起来。 那么&#xff0c;这近两年的发展&#xff0c;卫星相关的功能都有了怎样的变化呢&…

HarmonyOS NEXT的发布不仅打破了技术垄断,还保护了国人数据安全

吉祥学安全知识星球&#x1f517;除了包含技术干货&#xff1a;Java代码审计、web安全、应急响应等&#xff0c;还包含了安全中常见的售前护网案例、售前方案、ppt等&#xff0c;同时也有面向学生的网络安全面试、护网面试等。 01-HarmonyOS NEXT 前几天华为发布了最新的鸿蒙…

P8813 [CSP-J 2022] 乘方

题目&#xff1a; P8813 [CSP-J 2022] 乘方 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 提交记录&#xff1a; 记录详情 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 个人主页&#xff1a; xuzb 的个人中心 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) AC代码&…