【Linux】进程间通信2——命名管道

news2024/11/27 4:24:04

1. 命名管道(FIFO)

1.1. 基本概念

简单,给匿名管道起个名字就变成了命名管道

那么如何给 匿名管道 起名字呢?

  • 结合文件系统,给匿名管道这个纯纯的内存文件分配 inode,将文件名与之构建联系,关键点在于不给它分配 Data block,因为它是一个纯纯的内存文件,是不需要将数据刷盘到磁盘中的

可以将命名管道理解为 “挂名” 后的匿名管道,把匿名管道加入文件系统中,但仅仅是挂个名而已,目的就是为了让其他进程也能看到这个文件(文件系统中的文件可以被所有进程看到)

因为没有 Data block,所以命名管道这个特殊文件大小为 0

匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信,如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。

普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。

命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

不同于匿名管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中。

  1. 命名管道是一个文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。
  2. 值得注意的是,FIFO(first input first output)总是按照先进先出的原则工作,第一个被写⼊的数据将首先从管道中读出。

        匿名管道是在进程中由pipe()系统调用创建的管道文件. 对用户来说其实是不可见的. 也不能被其他毫无干系的进程打开. 只能通过pipe创建、打开

        而命名管道则不同命名管道对用户来说, 是可见的, 也就是说在进程内是可以指定路径打开的 , 这也是 命名管道可以实现 毫不相干的进程之间通信的原因

1.2.毫不相干的进程之间是怎么通信的

命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

虽然命名管道看起来很像文件,但是本质上这个文件也是一个内存文件,只不过在内存上只有一个简单的映像,永远都是 0 ,就只占了一个名字罢了。两个进程只需要通过这个名字在内存中打开文件就好了。

我们来验证一下

 我要完成的目的是在用户端 client 输入数据(从屏幕中读取去),而在客户端 server 接收客户端的数据,然后再从客户端打印出来。

        但是这里需要注意的是,我们要想进行通信,那么就必须可以看到同一份资源,所以这里我就将共同的资源放到了头文件里,然后两个程序就可以看到同一份资源了:

comm.h
  1 #pragma once
  2 #include <stdio.h>
  3 #include <unistd.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <sys/stat.h>                                                   
  7 #include <fcntl.h>
  8 
  9 #define FILE_NAME "myfifo"
server.c
  1 #include"comm.h"
  2                                                                         
  3 int main()
  4 {
  5   if(mkfifo(FILE_NAME, 0644) < 0){
  6     perror("myfifo");
  7     return 1;
  8   }
  9 
 10   int fd = open(FILE_NAME, O_RDONLY);
 11   if(fd < 0){
 12     perror("open");
 13     return 2;
 14   }
 15 
 16   char msg[128];
 17   while(1){
 18     msg[0] = 0;
 19     ssize_t s = read(fd, msg, sizeof(msg)- 1);
 20     if(s > 0){
 21       msg[s] = 0;
 22       printf("client# %s\n", msg);
 23     }
 24     else if(s == 0){
 25       printf("client quit!\n");
 26       break;
 27     }
 28     else{
 29       printf("read error!\n");
 30       break;                                                             
 31     }
 32   }
 33   close(fd);
 34 
 35   return 0;
 36 }
 
client.c
  1 #include"comm.h"
  2 
  3 int main()
  4 {
  5   int fd = open(FILE_NAME, O_WRONLY);
  6   if(fd < 0){
  7     perror("open");
  8     return 1;                                                           
  9   }                                                  
 10                                                  
 11   char msg[128];  
 12   while(1){                                   
 13     msg[0] = 0;                      
 14     printf("Please Enter# ");
 15     fflush(stdout);                     
 16     ssize_t s = read(0, msg, sizeof(msg));
 17     if(s > 0){
 18       msg[s] = 0;                                                  
 19       write(fd, msg, strlen(msg));                               
 20     }                                                     
 21   }                                   
 22                                                         
 23   close(fd);                                  
 24   return 0;
 25 }                             

我们可以看到这样的效果。

   而且我们会发现,这两个进程是没有任何关系的,但是它们两个就可以进行通信。而且除了 server 创建一个管道文件,其它的操作和操作普通文件没有区别。

        那么接下来就操作:情况是不是在命名管道开头的那幅图的情况:

        首先先把 server 中读取的操作去掉:

  然后进行下方操作:

我们可以看到,我已经写了好多消息了,但是我们发现 myfifo 的大小一直都是 0 ,换句话说就是我写的消息已经被写到管道的缓冲区里了,但是 server端 并没有读取,所以写的消息还是在内存里,但是又因为 myfifo 的大小为 0 ,就说明数据并没有刷新到磁盘,也就意味着,双方通信依旧是在内存中通信的,和匿名管道的底层原理是一样的,它们采用的都是文件通信。

        所以这下也就更能理解上面的那副图了吧。

1.3.命名管道的工作原理

1.4、命名管道与匿名管道的区别

不同点:

  1. 匿名管道只能用于具有血缘关系的进程间通信;而命名管道不讲究,谁都可以用
  2. 匿名管道直接通过 pipe 函数创建使用;而命名管道需要先通过 mkfifo 函数创建,然后再通过 open 打开使用
  3. 出现多条匿名管道时,可能会出现写端 fd 重复继承的情况;而命名管道不会出现这种情况

在其他方面,匿名管道与命名管道几乎一致

        两个都属于管道家族,都是最古老的进程间通信方式,都自带同步与互斥机制,提供的都是流式数据传输

2.创建命名管道

2.1.mkfifo命令

我们可以使用mkfifo命令来创建命名管道

mkfifo 命名管道的名字

 

成功解锁了一种新的特殊类型文件:p 管道文件

这个管道文件也非常特殊:大小为 0,从侧面说明 管道文件就是一个纯纯的内存级文件,有自己的上限,出现在文件系统中,只是单纯挂个名而已

 

2.2.mkfifo函数 

 此外我们还可以通过相关的函数来创建,这个函数也叫mkfifo 

​
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *path,mode_t mode);

path为创建的命名管道的全路径名:
mod为指定了文件的读写权限; 

成功都返回0,失败都返回-1

​

 参数 : mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)

mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。

返回值:    命名管道创建成功,返回0   ;   命名管道创建失败,返回-1。

3.命名管道的使用

命名管道和管道的使用方法法基本是相同的。

只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而管道是存在于内存中的特殊文件。

需要注意的是,调用open()打开命名管道的进程可能会被阻塞。

  1. 但如果同时用读写方式( O_RDWR)打开,则一定不会导致阻塞;
  2. 如果以只读方式( O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写方打开管道;
  3. 同样以写方式( O_WRONLY)打开也会阻塞直到有读方式打开管道。

①如果当前打开操作是为读而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
  • O_NONBLOCK enable:立刻返回成功。

②如果当前打开操作是为写而打开FIFO时。

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。

4.命名管道的四个使用示例 

(1)通过命名管道 ,client & server进行通信

①comm.h中包含一些头文件供client 和 server使用

//comm.h
#pragma once
 
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
 
#define FILE_NAME "myfifo" //让客户端和服务端使用同一个命名管道

②server.c 代码

实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,我们需要让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。

#include "comm.h"
 
 
int main()
{
  if(mkfifo(FILE_NAME , 0644) < 0){ //创建管道文件
    perror("mkfifo");
  }
 
 int fd = open(FILE_NAME , O_RDONLY); //只读形式打开
 if(fd < 0){
   perror("open error!\n");
   return 2;
 }
 
 char buf[128];
 while(1){
   ssize_t s = read(fd ,buf, sizeof(buf)-1); //开始读数据
   if(s > 0){
      buf[s] = 0;
      printf("client# %s\n" , buf);
   }
   else if(s == 0){
      printf("client quit!\n");
      break;
   }
   else{
      printf("read error!\n");
      break;
   }
 }
 
 close(fd);
  return 0;
}

③client.c 代码

 而对于客户端来说,因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信。 

#include "comm.h"
 
 
int main()
{
  int fd = open(FILE_NAME , O_WRONLY); //以写的方式打开文件
  if(fd < 0){
    perror("open error!\n");
    return 1;
  }
 
  char buf[128];
  while(1){
    printf("Please Enter# ");  //提示语句
    fflush(stdout);
    ssize_t s = read(0 , buf , sizeof(buf));  //从键盘中读取数据
   if(s > 0){
     buf[s-1] = 0; //输入时多了一个\n
     write(fd , buf ,strlen(buf)); //把读取到的数据写到管道中
   }
 
  }
 
  return 0;
}

④运行结果

客户端写入的信息进入命名管道当中,服务端再从命名管道当中将信息读取出来打印在服务端的显示器上,该现象说明服务端是能够通过命名管道获取到客户端发来的信息的,换句话说,此时这两个进程之间是能够通信的。

通过ps命令查看这两个进程的信息,可以发现这两个进程确实是两个毫不相关的进程,因为它们的PID和PPID都不相同 

 ⑤client 和 server 谁先退出问题

1) 客户端先退出,服务端将管道当中的数据读完后就再也读不到数据了,那么此时服务端也就会去执行它的其他代码了(在当前代码中是直接退出了)。

2)服务端先退出,客户端写入管道的数据就不会被读取了,也就没有意义了,那么当客户端下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时客户端就被操作系统强制杀掉了。

⑥通信是在内存当中进行的

client端代码不变 ,server端只是以读的方式打开,但是不读取数据  :

 运行程序前后两次查看myfifo管道文件的大小始终为0,说明了双方进程之间的通信依旧是在内存当中进行的,和匿名管道通信是一样的。

(2)通过命名管道,派发计算任务

①两个进程之间的通信,并不是简单的发送字符串而已,服务端是会对客户端发送过来的信息进行某些处理的 ; 这里我们的client端发送计算任务,server端将数据计算出来并打印到显示器上。

②client端的代码没有发生变化,只是server端读取数据时对数据进行了一些处理:

#include "comm.h"
 
 
int main()
{
  if(mkfifo(FILE_NAME , 0644) < 0){ //创建管道文件
    perror("mkfifo");
  }
 
 int fd = open(FILE_NAME , O_RDONLY); //只读形式打开
 if(fd < 0){
   perror("open error!\n");
   return 2;
 }
 
 char buf[128];
 while(1){
   ssize_t s = read(fd ,buf, sizeof(buf)-1); //开始读数据
   if(s > 0){
      buf[s] = 0;
 
     //简单计算
     char *p = buf;
     const char *lable="+-*/%";
     int flag = 0; //记录计算的符号,利用下标 
     while(*p){
         switch(*p){
             case '+':
                 flag = 0;
                 break;
             case '-':
                 flag = 1;
                 break;
             case '*':
                 flag = 2;
                 break;
             case '/':
                 flag = 3;
                 break;
             case '%':
                 flag = 4;
                 break;
         }
         p++;
     }
 
     char *data1 = strtok(buf, "+-*/%"); //通过算数符号将左右两个数字分开
     char *data2 = strtok(NULL, "+-*/%");
     int x = atoi(data1); //将字符转整形计算
     int y = atoi(data2);
     int z = 0;
     switch(flag){
         case 0:
             z = x + y;
             break;
         case 1:
             z = x - y;
             break;
         case 2:
             z = x * y;
             break;
         case 3:
             z = x / y;
             break;
         case 4:
             z = x % y;
             break;
     }
 
     printf("%d %c %d = %d\n", x,lable[flag], y, z);
   }
   else if(s == 0){
      printf("client quit!\n");
      break;
   }
   else{
      printf("read error!\n");
      break;
   }
 }
 
 close(fd);
  return 0;
}

③结果

(3)通过命名管道,进行命令操作

①我们可以通过一个进程来控制另一个进程的行为,比如我们从客户端输入命令到管道当中,再让服务端将管道当中的命令读取出来并执行。简单实现了让服务端执行不带选项的命令,若是想让服务端执行带选项的命令,可以对管道当中获取的命令进行进一步的解析处理。

     ②client端的代码不变,server端对于输入的数据进行解析,如果有命令则执行:

#include "comm.h"
 
 
int main()
{
  if(mkfifo(FILE_NAME , 0644) < 0){ //创建管道文件
    perror("mkfifo");
  }
 
 int fd = open(FILE_NAME , O_RDONLY); //只读形式打开
 if(fd < 0){
   perror("open error!\n");
   return 2;
 }
 
 char buf[128];
 while(1){
   ssize_t s = read(fd ,buf, sizeof(buf)-1); //开始读数据
   if(s > 0){
      buf[s] = 0;
      printf("client# %s\n" , buf);
 
     //执行命令
      if(fork() == 0){ //child
         execlp(buf , buf ,NULL); //进程替换
         exit(1);
      }
 
      waitpid(-1 , NULL ,0); //进程等待
 
   }
   else if(s == 0){
      printf("client quit!\n");
      break;
   }
   else{
      printf("read error!\n");
      break;
   }
 }
 
 close(fd);
  return 0;
}

(4)通过命名管道,进行文件拷贝

①大致思路是,client端将log.txt 文件通过管道发送给server端,server端读取管道中的数据创建一个本地文件,将数据拷贝到本地文件中,以此来实现文件的拷贝。(本实验是在同一个机器上,且在同一个目录下,所以发送文件的文件名不能和接受文件的文件名重复)

②client端代码 : 以读的方式打开log.txt文件 , 以写的方式打开管道文件 ,将log.txt文件中的数据写到管道中.

#include "comm.h"
 
 
int main()
{
  int fd = open(FILE_NAME, O_WRONLY); //以写的方式打开命名管道文件
	if (fd < 0){
		perror("open");
		return 1;
	}
 
	int fdin = open("log.txt", O_RDONLY); //以读的方式打开log.txt文件
	if (fdin < 0){
		perror("open");
		return 2;
	}
 
	char buf[128];
	while (1){
		//从log.txt文件当中读取数据
		ssize_t s = read(fdin, buf, sizeof(buf));
		if (s > 0){
			write(fd, buf, s); //将读取到的数据写入到命名管道当中
		}
		else if (s == 0){
			printf("read end of file!\n");
			 break;
		}
		else{
			printf("read error!\n");
			break;
		}
	}
 
	close(fd); //通信完毕,关闭命名管道文件
	close(fdin); //数据读取完毕,关闭log.txt文件
	return 0;
}

③server端代码 : 以读的方式打开管道文件(没有管道文件创建) , 以写的方式在本地创建一个log-bat.txt文件,并将管道中的数据写到log-bat.txt文件中。

#include "comm.h"
 
 
int main()
{
  if(mkfifo(FILE_NAME , 0644) < 0){ //创建管道文件
    perror("mkfifo");
  }
 
 int fd = open(FILE_NAME , O_RDONLY); //只读形式打开
 if(fd < 0){
   perror("open error!\n");
   return 2;
 }
 
 int fdout = open("log-bat.txt" , O_WRONLY|O_CREAT , 0644);
 if(fdout < 0){
   perror("open error!\n");
   return 3;
 }
 
 char buf[128];
 while(1){
   ssize_t s = read(fd ,buf, sizeof(buf)-1); //开始读数据
   if(s > 0){
    write(fdout , buf , s); //将数据从管道写入文件
   }
   else if(s == 0){
     printf("client quit!\n");
     break;
   }
   else{
     printf("read error!\n");
     break;
   }
 }
 
  close(fd); //通信关闭,关闭管道文件描述符
  close(fdout); //数据写入完毕
  return 0;
}

⑤进一步理解文件拷贝 

使用管道在本地进行的文件拷贝,所以看似没什么意义,但我们若是将这里的管道想象成“网络”,将客户端想象成“Windows Xshell”,再将服务端想象成“centos服务器”, 那我们此时实现的就是文件上传的功能,若是将方向反过来,那么实现的就是文件下载的功能。

(5). 命名管道和匿名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,由open函数打开。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

5.命令行中的管道理解

(1)使用cat 和 grep 命令 , 利用 管道 “ | ” 对信息进行过滤。

(2)管道“ | ” 是匿名管道还是命名管道 ?

①由于匿名管道只能用于有亲缘关系的进程之间的通信,而命名管道可以用于两个毫不相关的进程之间的通信,因此我们可以先看看命令行当中用管道(“|”)连接起来的各个进程之间是否具有亲缘关系。

②通过管道连接了三个进程,通过ps命令查看这三个进程可以发现,这三个进程的PPID是相同的,也就是说它们是由同一个父进程创建的子进程。

③三个sleep进程的父进程是bash ,三个sleep进程互为兄弟

④结论

若是两个进程之间采用的是命名管道,那么在磁盘上必须有一个对应的命名管道文件名,而实际上我们在使用命令的时候并不存在类似的命名管道文件名,因此命令行上的管道实际上是匿名管道。

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

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

相关文章

python+selenium之点击元素报错:‘NoneType‘ object has no attribute ‘click‘

今日遇到一个很奇怪的问题 case1:当使用顺序结构直接从登录到点击页面菜单&#xff0c;则可以正常点击菜单 case2&#xff1a;若把登录分离开&#xff0c;采用封装的方法点击菜单则会提示&#xff1a;‘NoneType’ object has no attribute ‘click’ 具体页面如下&#xff0c…

Vue01-前端概述

一、前端核心分析 1.1、概述 Soc原则&#xff1a;关注点分离原则 Vue 的核心库只关注视图层&#xff0c;方便与第三方库或既有项目整合。 HTML CSS JS : 视图 &#xff1a; 给用户看&#xff0c;刷新后台给的数据 网络通信 &#xff1a; axios 页面跳转 &#xff1a; v…

发那科机器人IO 分配

IO 信号 也称为输入\输出信号&#xff0c;是机器人与外围设备通信的电信号

Android 断点续传基础之单线程下载

**遇到的问题&#xff1a;**在这因为返回值的问题烦躁了一下&#xff0c;有可能出现空指针的异常&#xff0c;已经提出成文章了 请参考http://blog.csdn.net/qq_27489007/article/details/53523378 文件关系图 断点续传流程图 开始撸代码(主要代码) /** 普通断点续传 */ …

FirstChip1179量产经历(附FirstChipMpTools_v20200430工具)

前言&#xff1a; 新工作的原因&#xff0c;需要频繁用到u盘&#xff0c;手头上有2个常用的U盘&#xff08;龙凤呈祥57.3G&#xff08;64G&#xff09;&#xff09; 一个用来存放课件ppt、word、excel等文件&#xff1b;一个用来存放一些软件工具&#xff1b; ps&#xff0c…

将Typora中图片从指定路径移动到当前文件夹下(准确位置为:XX.md文件所在目录/XX.assets/)

1、背景介绍 最开始时候,将复制到Typora中的图片放在了指定位置。但是在进行迁移(在别的电脑上打开md文件),会发现发送过来的文件不包含图片,导致文件内容缺失。解决办法就是将图片放在XX.md文件所在目录/XX.assets/。 2、方法 2.1 做好文件备份 2.2 替换图片链接中的…

又一个SQL Developer中调试存储过程的例子

此例基于OBE&#xff08;Oracle By Example&#xff09;的示例。是在SQL Developer中调试存储过程一文的进阶。复习了前文的一些基本概念&#xff0c;并纠正了官方示例的一个错误。 本文使用的是标准的HR 示例 Schema&#xff0c;调试的存储过程源代码如下&#xff1a; CREAT…

针对k8s集群已经加入集群的服务器进行驱逐

例如k8s 已经有很多服务器&#xff0c;现在由于服务器资源过剩&#xff0c;需要剥离一些服务器出来 查找节点名称&#xff1a; kubectl get nodes设置为不可调度&#xff1a; kubectl cordon k8s-node13恢复可调度 kubectl uncordon k8s-node13在驱逐之前先把需要剥离驱逐的节…

Vite+Vue3安装且自动按需引入Element Plus组件库

一&#xff0c;安装Element Plus npm install element-plus //node环境16二&#xff0c;安装插件 npm install unplugin-auto-import unplugin-vue-components -D三&#xff0c;配置vite.config.ts文件 //按需引入element-plus组件 import AutoImport from unplugin-auto-i…

电手出软件啦!各种姿势一键重装你的系统

电手的兄弟们你们好啊&#xff0c;很多兄弟都知道&#xff0c;我们老大傲梅是做软件的&#xff0c;例如大伙儿都用过的傲梅分区助手&#xff0c;还有傲梅轻松备份。相信不少兄弟都是用了分区助手才关注到电手的。 而我们电手&#xff0c;在电脑和手机玩机内容方面一直深耕。 跟…

9.2.2 DeepLab系列模型中每一代的创新是什么?是为了解决什么问题?

9.2.2 DeepLab系列模型中每一代的创新是什么&#xff1f;是为了解决什么问题&#xff1f; 前情回顾&#xff1a;9.2.1 简述图像分割中经常用到的编码器-解码器网络结构的设计理念。 DeepLab是Google 团队提出的一系列图像分割算法。 DeepLab v1在2014年被提出&#xff0c;并在…

SpringMVC系列十一: 文件上传与自定义拦截器

文章目录 SpringMVC文件上传基本介绍需求分析 / 图解应用实例-代码实现 自定义拦截器什么是拦截器自定义拦截器执行流程分析图自定义拦截器应用实例快速入门注意事项和细节Debug执行流程 多个拦截器多个拦截器执行流程示意图应用实例1代码实现注意事项和细节 应用实例2 作业布置…

汇编语言程序设计 - 新建一个文件:d:\abc.txt,从键盘输入文件的内容(不超过100个字符)

80x86汇编习题 题目描述&#xff1a;编写一个程序&#xff0c;新建一个文件&#xff1a;d:\abc.txt&#xff0c;从键盘输入文件的内容&#xff08;不超过100个字符&#xff09; 思路&#xff1a; 1&#xff0c;定义好文件名&#xff0c;记得末尾0 2&#xff0c;定义好缓冲区…

C#开发-集合使用和技巧(六)特殊转换方法SelectMany的介绍和用法

介绍 SelectMany 方法在C#中用于将集合中的元素转换为其他类型的集合&#xff0c;并将这些集合扁平化为一个单一的序列。它是LINQ的一部分&#xff0c;允许你在一个序列上进行投影和过滤操作&#xff0c;然后将结果合并成一个序列。 方法定义 public static IEnumerable<…

【Python机器学习实战】 | 基于决策树的药物研究分类预测

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

Python内置debug库: pdb用法详解

文章目录 0. 引言1. 基本用法1.1 设置断点1.2 通过命令行启动 pdb 2. 常用命令2.1 n (next)2.2 s (step)2.3 c (continue)2.4 l (list)2.5 p (print)2.6 h (help)2.7 b (break)2.8 cl (clear)2.9 q (quit) 3. 例子 0. 引言 pdb&#xff08;Python Debugger&#xff09;是Pytho…

【Redis】分布式锁基本理论与简单实现

目录 分布式锁解释作用特性实现方式MySQL、Redis、Zookeeper三种方式对比 原理 reids分布式锁原理目的容错redis简单分布式锁实现锁接口实现类下单场景的实现容错场景1解决思路优化代码 容错场景2Lua脚本Redis利用Lua脚本解决多条命令原子性问题 释放锁的业务流程Lua脚本来表示…

SpringMVC系列九: 数据格式化与验证及国际化

SpringMVC 数据格式化基本介绍基本数据类型和字符串自动转换应用实例-页面演示方式Postman完成测试 特殊数据类型和字符串自动转换应用实例-页面演示方式Postman完成测试 验证及国际化概述应用实例代码实现注意事项和使用细节 注解的结合使用先看一个问题解决问题 数据类型转换…

游泳耳机哪个牌子好性价比高?精选高性价比的四大游泳耳机!

在现代社会中&#xff0c;随着健身和水中运动的普及&#xff0c;游泳耳机作为一种关键的健身配件&#xff0c;正日益受到广泛关注和需求。无论是在游泳池畅游还是深潜海底&#xff0c;好的游泳耳机不仅能提供高品质的音乐享受&#xff0c;更能保护耳朵免受水压和湿润环境的侵害…

训练营第四十一天| 1035.不相交的线53. 最大子序和392.判断子序列115.不同的子序列

1035.不相交的线 力扣题目链接(opens new window) 我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 现在&#xff0c;我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线&#xff0c;只要 A[i] B[j]&#xff0c;且我们绘制的直线不与任何其他连线&#xff08;…