Linux——进程间通信之管道

进程间通信之管道

在这里插入图片描述

文章目录

  • 进程间通信之管道
  • 1. 进程间通信
    • 1.1 为什么要进行进程间的通信
    • 1.2 如何进行进程间的通信
    • 1.3 进程间通信的方式
  • 2. 管道
    • 2.1 匿名管道
      • 2.1.1 系统调用pipe()
      • 2.1.2 使用匿名管道进行通信
      • 2.1.1 匿名管道四种情况
      • 2.1.2 匿名管道的五大特性
      • 2.1.3 进程池
    • 2.2 命名管道
      • 2.2.1 创建命名管道
      • 2.2.2 利用命名管道进行进程间通信
      • 2.2.3 命名管道的一个特性

1. 进程间通信

1.1 为什么要进行进程间的通信

对于这个问题,答案显而易见:一个进程必然不能解决所有的问题,系统中往往需要多个进程的协作来进行工作,而进程间的协作就需要进程之间进行信息的交互,这个过程也叫做进程间的通信

简单来说,进程间的通信可以实现以下功能:

  • 数据传输
  • 进程控制
  • 资源共享
  • 事件通知
  • ………………

1.2 如何进行进程间的通信

由于进程具有独立性,因此两个进程之间不能直接进行数据之间的传输(否则就会破坏进程的独立性)

因此我们需要一片公共的区域来供进程之间进行信息交流,而这片公共区域就是由OS操作系统提供

根据上面的分析,我们也可以得出关于进程间通信的本质:

进程间通信的实质上就是:让不同的进程看到同一份资源

1.3 进程间通信的方式

根据操作系统OS提供公共区域方式的不同,进程间的通信也会有不同的形式,例如:

  • 管道
  • 共享内存
  • 信号量
  • 消息队列

2. 管道

我们来假设这样一个场景:

一个进程同时以读写的方式打开一个文件,那么就会产生两个文件描述符fd:

在这里插入图片描述

此时,创建子进程,子进程会继承父进程属性:

在这里插入图片描述

我们可以发现,这时子进程和父进程就同时看到同一份文件file了。这时,如果我们关闭父进程的写端,再关闭子进程的读端:

在这里插入图片描述

这样,就可以通过子进程向公共文件写数据,父进程向公共文件读数据的方式,进行父子进程之间的通信了。


向上面这样的,基于文件的形式进行进程间通信的方式,叫做管道

通过上面的例子,我们也知道:管道只允许一端读,一端写,因此基于管道的通信都是单向的

管道可以分为匿名管道和命名管道

2.1 匿名管道

2.1.1 系统调用pipe()

系统不允许用磁盘文件当作进程间通信的公共资源,为此系统提供了系统调用pipe()来为我们创建进程通信需要的文件:

#include <unistd.h>
int pipe(int pipefd[2]);
  • 该系统调用会创建并以读写方式打开一个不存在于磁盘且不需要向磁盘刷新的,只存在于内存中的匿名文件(因为这个文件只需用来给两个进程进行通信,不需要持久的保存),这个文件就叫做匿名管道
  • 数组pipefd是一个输出型参数,用于存放匿名管道的fd,其中pipefd[0]存放的是读端;pipefd[1]存放的是写端

2.1.2 使用匿名管道进行通信

使用pipe()创建了系统调用后,我们再创建一个子进程,让子进程继承父进程的数据,此时父子进程就会同时以读写的方式指向同一个匿名管道了。最后只要确定数据传输方向,关闭父进程的读(写)端,关闭子进程的写(读)端,就可以正常进程进程间的通信

同时,我们应该清楚:

由于是通过创建子进程的方式,来让子进程继承父进程的数据,使其指向相同的匿名管道来进行通信,因此匿名管道只能用来进行血缘进程之间的通信(通常用于父子进程)

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)	//写端操作
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 20){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)	//读端操作
{char buf[1024] = {0};int cnt = 20;while(cnt--){read(rfd, buf, sizeof(buf) - 1); //-1:预留'\0',防止出现错误printf("%s\n", buf);}
}int main()
{int pipefd[2] = {0};	int ret = pipe(pipefd);	//创建匿名管道if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id)	//子进程{//chile(w)close(pipefd[0]);	//关闭读端my_write(pipefd[1]);	//进行写操作exit(-1);}//father(r)close(pipefd[1]);	//关闭写端my_read(pipefd[0]);	//进行读操作wait(NULL);	//等待子进程退出,防止出现僵尸进程return 0;
}

效果如图所示:

在这里插入图片描述

2.1.1 匿名管道四种情况

情况一:写端停止写入并且管道没有数据,那么读端就会阻塞,直到读到数据

例如,将上述代码的my_write()函数修改:

void my_write(int wfd)
{//写端不写
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到,如果写端不向读端写入数据,并且管道没有数据,读端就会陷入阻塞等待状态

情况二:写端一直在写直到管道写满,读端不读,那么写端就会阻塞等待,直到管道的数据被读取

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{int cnt = 0;char a = 'A';//每次只写入一个字符,并输出输入的字符个数while(1){write(wfd, &a, 1);printf("cnt: %d\n", cnt++);}
}void my_read(int rfd)
{//读端不读
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到:当读端不读时,写端向管道写入了65535 + 1个字符后,就进入了阻塞等待状态,这说明管道已经被填满了。同时也可以推出,在博主所用的系统中,默认的管道大小为65536Byte也就是64kB

如果我们再次修改代码,让读端每2秒读取一次管道:

void my_read(int rfd)
{char buf[1024] = {0};while(1){sleep(2);int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)break;printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

可以总结:当管道的部分数据被读取后,写端有重新写入数据了

情况三:写端关闭,当读端读完管道的数据后,读端就会读到管道的末尾(相当于读到文件尾),自动关闭读端

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 5){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};while(1){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)	//如果read的返回值为0,说明写端关闭了fd,读端读到了管道末尾{printf("no data be read, read exit\n");break;}printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

情况四:读端关闭,写端还在写,系统就会通过发送信号的方式强制终止写端(kill -13 child_pid)

将整体代码修改如图:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)
{const char* str = "i am child process: ";char buf[128] = {0};pid_t id = getpid();while (1){snprintf(buf, sizeof(buf), "message: %s, id: %d\n", str, id);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};int cnt = 5;while(cnt--){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n){printf("no data be read, read exit\n");break;}printf("%s\n", buf);}printf("read will close\n");sleep(1);close(rfd);
}int main()
{int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id){//chile(w)close(pipefd[0]);my_write(pipefd[1]);printf("child close wfd\n\n");close(pipefd[1]);exit(-1);}//father(r)close(pipefd[1]);my_read(pipefd[0]);int status = 0;int rid = waitpid(id, &status, 0);if (rid == id){printf("child process singal code: %d\n", status & 0x7f);}return 0;
}

重新编译后,运行效果如图:

在这里插入图片描述

2.1.2 匿名管道的五大特性

  • 同步机制:管道在处理数据读写时,确保数据的有序性和正确性的一种控制方式
  • 血缘进程通信:由于匿名管道的构建建立在进程继承的基础上,因此匿名管道只允许血缘进程的通信
  • 单向通信(半双工):同一时间,只允许一端读,一端写
  • 文件的生命周期是随进程的:父子进程退出,管道(文件)自动释放
  • pipe是面向字节流的

2.1.3 进程池

进程池的基本概念

进程池是一组预先创建好的进程集合,这些进程处于空闲等待状态,随时准备接收任务并进行处理。父进程可以在任意时候控制子进程的休眠、工作与退出。

进程池的优点

  • 可以复用进程,从而避免了频繁的调用系统函数,节省了资源开销与时间
  • 使用进程池有利于管理,并充分利用系统资源

实现进程池:

进程池程序myprocesspool的程序流程大致如下:

在这里插入图片描述

实现代码:

task.hpp:

#pragma once#include<iostream>
#include<unistd.h>typedef void(*work_t)(pid_t);
typedef void(*task_t)(pid_t);void task_1(pid_t id)
{std::cout << "task_1" << std::endl;
}void task_2(pid_t id)
{std::cout << "task_2" << std::endl;
}void task_3(pid_t id)
{std::cout << "task_3" << std::endl;
}task_t tasks[3] = {task_1, task_2, task_3};void worker(int channel_index)
{while(1){int task_index = 0;int n = read(0, &task_index, sizeof(int));if (0 == n){std::cout << "write close, channel: " << channel_index << " closing…………" << std::endl; break;}std::cout << "channel: " << channel_index << "  pid: " << getpid() << ": i am working: ";tasks[task_index](getpid());}
}

processpool.cc:

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctime>
#include "task.hpp"#define TIME 5//return value
enum
{USAGE_ERROR = 1,PROCESS_NUM_ERROR, PIPE_ERROR,SELECT_CHANNEL_ERROR,SELEDT_TASK_ERROR
};void usage()
{std::cout << "Usage: ./processpool [process_num]" << std::endl;
}class channel
{
public:channel(std::string name, int rfd, int wfd, pid_t child_id): _name(name), _rfd(rfd), _wfd(wfd), _child_id(child_id){}const std::string& get_name(){return _name;}int get_rfd(){return _rfd;}int get_wfd(){return _wfd;}pid_t get_child_id(){return _child_id;}void close_fd(){close(_wfd);}
private:const std::string _name;const int _rfd;const int _wfd;const pid_t _child_id;
};class processpool
{
public:processpool(int process_num): _process_num(process_num){}//创建管道int create_channel(){std::vector<int> fd;for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return PIPE_ERROR;}pid_t id = fork();if (id == 0){//child: readclose(pipefd[1]);dup2(pipefd[0], 0);//关闭除第一个子进程外的多个写端if (!fd.empty()){for (auto k : fd)close(k);}//workworker(i);std::cout << "channel " << i << " pid: " << getpid() <<  " exit" << std::endl;exit(-1);}//father: writeclose(pipefd[0]);std::string name = std::string("channel: ") + std::to_string(i);channel c = channel(name, pipefd[0], pipefd[1], id);_channels.push_back(c);fd.push_back(pipefd[1]);}return 0;}    int select_channel(){static int c = 0;int ret = c;c = (++c) % _process_num;return ret;}int select_task(){return rand() % 3;}int control_child(){int cnt = TIME;while(cnt--){//选择一个进程(管道),一个任务int channel_index = select_channel();int task_index = select_task();if (channel_index >= _process_num){std::cout << "select channel error" << std::endl;return SELECT_CHANNEL_ERROR;}if (task_index >= 3){std::cout << "select task error" << std::endl;return SELEDT_TASK_ERROR;}//发送任务int wfd = _channels[channel_index].get_wfd();write(wfd, &task_index, sizeof(int));sleep(2);}return 0;}void PrintDebug(){for (auto channel: _channels){std::cout << channel.get_name() << ": rfd: " << channel.get_rfd() << ": child_id: " << channel.get_child_id() << std::endl;}}void clean_wait(){//关闭所有的写端for (auto channel : _channels){channel.close_fd();pid_t id = waitpid(channel.get_child_id(), nullptr, 0);if (-1 == id){perror("waitpid:");}if(id == channel.get_child_id()){std::cout << "wait child: " << channel.get_child_id() << " success\n" << std::endl;}}}
private:const int _process_num;std::vector<channel> _channels;
};//  ./process num
int main(int argc, char* argv[])
{if (1 == argc){usage();return USAGE_ERROR;}int process_num = std::stoi(argv[1]);if (process_num <= 0){std::cout << "process_num should be grearter than 0" << std::endl;return PROCESS_NUM_ERROR;}srand((unsigned int)time(nullptr));std::cout << "process nums: " << process_num << std::endl;//创建进程池processpool* processpool_1 = new processpool(process_num);//创建管道int ret = processpool_1->create_channel();   if (0 != ret){return ret;}std::cout << "create channels complete" << std::endl; //工作ret = processpool_1->control_child();if (0 != ret){return ret;}//回收 processpool_1->clean_wait();return 0;
}

2.2 命名管道

上文提到,匿名管道只适用于血缘进程之间的通信,那么为了解决没有关系进程间通信的问题,操作系统提供了命名管道

2.2.1 创建命名管道

使用命令创建命名管道:

mkfifo [filename]

例如:

在这里插入图片描述

可以看到,通过命令mkfifo fifo在当前目录创建了一个名为fifo的管道文件,同时可以看到,这个管道文件的文件类型为p(pipe)

使用系统调用创建命名管道:

int mkfifo(const char *pathname, mode_t mode);
  • pathname是管道文件的文件名
  • mode是管道文件的权限
  • 返回值:成功返回0;失败返回-1

2.2.2 利用命名管道进行进程间通信

知道了系统调用后,我们就可以利用管道来进行进程间的通信了:

我们用下面的代码进行演示:

头文件:

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define PATH "FIFO"	//默认文件名
#define MODE 0666	//默认权限//命名管道类
class fifo
{
public:fifo(const std::string& name)	//构造函数创建管道文件: _name(name){int ret = mkfifo(_name.c_str(), MODE);if (-1 == ret){std::cerr << "mkfifo error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}std::cout << "fifo made success" << std::endl;}~fifo(){unlink(_name.c_str());	//进程结束时,利用系统调用unlink()删除管道文件}
private:const std::string _name;
};

服务端server:读

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;	fifo named_pipe(PATH);	//服务端创建管道文件,读端int rfd = open(PATH, O_RDONLY);	char buffer[1024] = {0};while (1){int n = read(rfd, buffer, sizeof(buffer) - 1);if (0 == n)	//返回值为0,说明读到文件尾,即写端关闭{std::cout << "client exit, server also exit" << std::endl;break;}else if (-1 == n){std::cerr << "read error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}else{buffer[n] = 0;std::cout << buffer << std::endl;}}close(rfd);return 0;
}

客户端client:写

#include "common.hpp"int main()
{std::cout << "I am client" << std::endl;int wfd = open(PATH, O_WRONLY);	//客户端,写std::string buffer;while(1){printf("message # ");std::getline(std::cin, buffer);if ("quit" == buffer){printf("client exit\n");break;}int n = write(wfd, buffer.c_str(), buffer.size());if (n != buffer.size()){std::cerr << "write error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}}close(wfd);return 0;
}

效果如图:

在这里插入图片描述

2.2.3 命名管道的一个特性

如果我们在服务端server.cc代码中加一行代码:

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;fifo named_pipe(PATH);int rfd = open(PATH, O_RDONLY);std::cout << "open success" << std::endl;	//查看客户端是否成功打开管道文件//……………………}

并运行服务端(读端)一段时间后再打开客户端(写端):

在这里插入图片描述

可以看到:在服务端(读端)打开到客户端(写端)未打开的这段时间中,服务端(读端)并没有打开管道文件,而是等客户端(写端)启动后,再打开的管道。

通过这个现象,我们可以得出命名管道的一个特性:

当写端未打开而读端打开时,读端会阻塞,直至写端也打开


本篇完

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用chrome 访问虚拟机Apache2 的默认页面,出现了ERR_ADDRESS_UNREACHABLE这个鸟问题

本地环境 主机MacOs Sequoia 15.1虚拟机Parallels Desktop 20 for Mac Pro Edition 版本 20.0.1 (55659)虚拟机-操作系统Ubuntu 22.04 服务器版本 最小安装 开发环境 编辑器编译器调试工具数据库http服务web开发防火墙Vim9Gcc13Gdb14Mysql8Apache2Php8.3Iptables 第一坑 数…

海洋通信船舶组网工业4G路由器应用

船舶是浩瀚海洋中探索与贸易的载体&#xff0c;更是船员们生活与工作的家园。为了在广阔的水域中搭建起稳定、高效的网络桥梁&#xff0c;工业4G路由器以卓越的通信组网能力&#xff0c;为船舶组网提供网络支持。 工业4G路由器以其强大的信号发射能力&#xff0c;确保船舶内部…

分类算法——基于heart数据集实现

1 heart数据集——描述性统计分析 import matplotlib.pyplot as plt import pandas as pd# Load the dataset heart pd.read_csv(r"heart.csv", sep,)# Check the columns in the DataFrame print(heart.columns)aheart.loc[:, y].value_counts() print(a) heart.l…

力扣面试经典 150(上)

文章目录 数组/字符串1. 合并两个有序数组2. 移除元素3. 删除有序数组中的重复项4. 删除有序数组的重复项II5. 多数元素6. 轮转数组7. 买卖股票的最佳时机8. 买卖股票的最佳时机II9. 跳跃游戏10. 跳跃游戏II11. H 指数12. O(1)时间插入、删除和获取随机元素13. 除自身以外数组的…

Vue3-后台管理系统

目录 一、完成项目历程 1、构建项目 2、项目的自定义选项 3、 封装组件 4、配置对应页面的路由 5、从后端调接口的方式 二、引入Element Plus、Echarts、国际化组件 1、Element Plus安装 2、Echarts安装 3、国际化 三、介绍项目以及展示 1、项目是基于Vue3、Element …

mq 消费慢处理方式,rocketmq消费慢如何处理,mq如何处理消费端消费速率慢。rocketmq优化

1. 问题&#xff1a;mq消费慢&#xff0c;如何加快处理速度 2. 分析&#xff1a; 没想到吧&#xff0c;官网上就有处理方式。&#xff01; 3.链接&#xff1a; 基本最佳实践 | RocketMQ 4. 处理方式&#xff1a; 4.1 提高消费并行度 4.1.1 加机器&#xff0c;配置多个消费服…

内存级文件原理——Linux

目录 进程与文件 Linux下的文件系统 文件操作&#xff0c;及文件流 C语言函数 文件流 文件描述符 系统调用操作 系统调用参数 重定向与文件描述符 输出重定向 输入重定向 文件内容属性 Linux下一切皆文件 进程与文件 当我们对文件进行操作时&#xff0c;文件必…

MATLAB矩阵元素的修改及删除

利用等号赋值来进行修改 A ( m , n ) c A(m,n)c A(m,n)c将将矩阵第 m m m行第 n n n列的元素改为 c c c&#xff0c;如果 m m m或 n n n超出原来的行或列&#xff0c;则会自动补充行或列&#xff0c;目标元素改为要求的&#xff0c;其余为 0 0 0 A ( m ) c A(m)c A(m)c将索引…

并行IO接口8255

文章目录 8255A芯片组成外设接口三个端口两组端口关于C口&#xff08;★&#xff09; 内部逻辑CPU接口 8255A的控制字&#xff08;★&#xff09;位控字&#xff08;D70&#xff09;方式选择控制字&#xff08;D71&#xff09; 8255A的工作方式工作方式0&#xff08;基本输入/输…

springboot3如何集成knife4j 4.x版本及如何进行API注解

1. 什么是Knife4j knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案, 取名knife4j是希望她能像一把匕首一样小巧,轻量,并且功能强悍!knife4j的前身是swagger-bootstrap-ui,swagger-bootstrap-ui自1.9.6版本后,正式更名为knife4j为了契合微服务的架构发展,由于原来…

js高级06-ajax封装和跨域

8.1、ajax简介及相关知识 8.1.1、原生ajax 8.1.1.1、AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML&#xff0c;就是异步的 JS 和 XML。 通过 AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据。 按需请求&#xff0c;可…

自然色调人像自拍照后期Lr调色教程,手机滤镜PS+Lightroom预设下载!

调色教程 自然色调人像自拍照后期通过 Lightroom 调色&#xff0c;旨在打造出清新、自然、真实的人像效果。这种风格强调还原人物的本来面貌&#xff0c;同时增强照片的色彩和光影表现力&#xff0c;让自拍照更加生动和吸引人。 预设信息 调色风格&#xff1a;清晰透明风格预…

RabbitMQ简单应用

概念 RabbitMQ 是一种流行的开源消息代理&#xff08;Message Broker&#xff09;软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP - Advanced Message Queuing Protocol&#xff09;。RabbitMQ 通过高效的消息传递机制&#xff0c;主要应用于分布式系统中解耦应用…

计算机网络(14)ip地址超详解

先看图&#xff1a; 注意看第三列蓝色标注的点不会改变&#xff0c;A类地址第一个比特只会是0&#xff0c;B类是10&#xff0c;C类是110&#xff0c;D类是1110&#xff0c;E类是1111. IPv4地址根据其用途和网络规模的不同&#xff0c;分为五个主要类别&#xff08;A、B、C、D、…

挂壁式空气净化器哪个品牌的质量好?排名top3优秀产品测评分析

随着挂壁式空气净化器市场的不断扩大&#xff0c;各类品牌与型号琳琅满目。但遗憾的是&#xff0c;一些跨界网红品牌过于追求短期效益&#xff0c;导致产品在净化效果与去除异味方面表现平平&#xff0c;使用体验不佳&#xff0c;甚至可能带来二次污染风险&#xff0c;影响人体…

分布式 Data Warebase - 构筑 AI 时代数据基石

导读&#xff1a;作者以人类世界一个信息层次模型 DIKW 为出发点&#xff0c;引出对计算机世界&#xff08;系统&#xff09;处理数据过程的介绍。接着以一个民宿平台数据架构随业务发展而不断演进的过程&#xff0c;展示了这场信息革命中&#xff0c;在具体应用场景下&#xf…

如何将Latex的文章内容快速用word+Endnote排版

1 第一步 Endnote文件是无法直接导入bib文件的。需要将reference.bib的参考文献内容&#xff0c;通过JabRef软件打开并另存为refefence.ris文件 下载JabRef软件&#xff1a;https://www.jabref.org/#download 导出为ris格式文件 2 第二步 通过Endnote导入ris文件&#xff0…

[论文阅读] 异常检测 Deep Learning for Anomaly Detection: A Review(三)总结梳理-疑点记录

《深度异常检测综述》总结梳理 目录 一、研究背景与挑战二、深度异常检测方法分类三、实验评估四、结论在这篇文章中&#xff0c;**异常检测的异构性**主要从以下几个方面来理解&#xff1a;如何理解多源数据融合的困难“学习正常性的特征表示”与“用于特征提取的深度学习”在…

网络爬虫——爬虫项目案例

本节将全面讲解如何通过实战爬虫项目解决复杂问题。结合最新技术和实际开发需求&#xff0c;案例将涵盖完整开发流程&#xff0c;包括需求分析、实现代码、优化方法和常见问题解决。力求实现高效、可扩展的爬虫项目架构&#xff0c;帮助开发者提升实战能力。 案例 1&#xff1a…

实时质检-静音检测分析流程(运维人员使用)

前言 用户在实时质检时&#xff0c;开启了主叫或被叫静音检测功能&#xff0c;但是听录音时&#xff0c;主叫或被叫明明没有任何声音&#xff0c;但是通话没有被挂断。 说明主叫或被叫的静音阈值太低&#xff0c;导致系统没有把很小的声音认定为静音&#xff1b;或者检测非静音…