【Linux】system V共享内存 | 消息队列 | 信号量

news2024/11/18 19:51:55

​🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉system V共享内存👈
      • 共享内存的原理
      • 对共享内存的认识
      • 共享内存函数
    • 👉system V消息队列(了解)👈
    • 👉system V信号量👈
      • 进程互斥
    • 👉总结👈

👉system V共享内存👈

共享内存的原理

共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核。换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,进程如果要读写,直接进行内存即的读和写接口。而之前学习的 pipe 和 fifo 都要通过 read 和 write 系统调用才能进行通信,原因就是管道的本质是文件,文件是内核中的一种数据结构,由操作系统维护(3 到 4 G的内核空间),用户无权进行直接访问,只能通过系统调用来进行访问。注:堆栈之间的共享内存属于用户空间,内核空间是 3 到 4 G 之间的 1 G 内存空间。

在这里插入图片描述

在这里插入图片描述

对共享内存的认识

  • 共享内存不属于通信的任意一个进程,其属于操作系统,由操作系统所管理。
  • 管道的本质是文件,操作系统已经有相应的内核数据结构来管理文件,因此不需要再去设计新的内核数据结构去管理管道。而共享内存是专门为了进程间通信而设计的,操作系统可能会有很多共享内存,那么操作系统就需要将这些共享内存管理起来。
  • 管理的方式是先描述再组织,那么共享内存就等于共享内存块加上共享内存对应的内核数据结构。
  • 对共享内存的修改包括对属性的修改和对内容的修改。

共享内存内核数据结构

struct shmid_ds 
{
	struct ipc_perm shm_perm; /* operation perms */
	int shm_segsz; /* size of segment (bytes) */
	__kernel_time_t shm_atime; /* last attach time */
	__kernel_time_t shm_dtime; /* last detach time */
	__kernel_time_t shm_ctime; /* last change time */
	__kernel_ipc_pid_t shm_cpid; /* pid of creator */
	__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
	unsigned short shm_nattch; /* no. of current attaches */
	unsigned short shm_unused; /* compatibility */
	void *shm_unused2; /* ditto - used by DIPC */
	void *shm_unused3; /* unused */
};

共享内存函数

  • shmget 函数的功能是创建或获取共享内存,需要指明共享内存的权限,失败时的返回值是 -1。

在这里插入图片描述

  • ftok 函数的功能将 pathname 和 project id 经过一定的算法转化成 system V IPC key。pathname 必须存在,project id 不能为 0。失败时的返回值是 -1。

在这里插入图片描述

  • shmat 函数的功能是将共享内存段连接到进程地址空间(建立页表映射关系)。第一个参数是共享内存的标识符 shmid;第二个参数是指定连接的地址,为 nullptr 时,则让操作系统指定连接到合适的地址上;第三个参数是 shmflg,它的两个可能取值是 SHM_RND 和 SHM_RDONLY。shmflg 等于 SHM_RDONLY 时,表示连接操作用来只读共享内存。成功返回一个指针,指向共享内存第一个字节;失败返回 (void*) -1。

在这里插入图片描述
在这里插入图片描述

  • shmdt 函数的功能是将共享内存段与当前进程脱离。shmaddr 是由 shmat 函数所返回的指针,成功返回 0;失败返回 -1。注意:将共享内存段与当前进程脱离不等于删除共享内存段。
  • shmctl 函数的功能是用于控制共享内存。shmid 是由 shmget 函数返回的共享内存标识符;cmd 是将要采取的动作(有三个可取值);buf 为指向一个保存着共享内存的模式状态和访问权限的数据结构,不关心共享内存的内核数据结果是,buf 可以设置为 nullptr。

在这里插入图片描述
注:当进程运行结束,进程创建的共享内存还会存在。这是因为 system V IPC 资源的生命周期是随着其内核的,其内核可以通过代码删除(上述的 shmctl 函数),也可以通过 ipcrm -m shmid 指令手动删除共享内存。使用 ipcs -m 指令可以查看操作系统中已经创建的共享内存。

在这里插入图片描述

  • key 和 shmid 的区别:只有在创建共享内存时,使用到 key。大部分情况下,用户都是通过 shmid 来访问共享内存的。

客户端和服务端的模拟实现

# Makefile
.PHONY:all
all:shmClient shmServer

shmClient:shmClient.cc
	g++ -o $@ $^ -std=c++11
shmServer:shmServer.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shmClient shmServer
// Log.hpp
#ifndef _LOG_H_
#define _LOG_H_

#include <iostream>
#include <ctime>

#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3


const std::string msg[] = 
{
    "Debug",
    "Notice",
    "Warning",
    "Error"
};

std::ostream &Log(std::string message, int level)
{
    std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;
    return std::cout;
}

#endif

// Comm.h
#pragma once

#include <iostream>
#include <cstdio>
#include <cassert>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;  // 将std直接展开简答,但不推荐

#define PATH_NAME "/home/Joy"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE:4096KB)的整数倍

// 将十进制转为十六进制
string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

// shmServer.cc
#include "Comm.hpp"

int main()
{
    // 1. 创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << "server key: " << TransToHex(k) << endl;

    // 2. 创建共享内存(建议通信的发起者创建一个全新的共享内存)
    // 创建共享内存是也要指定权限
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        Log("create shm fail", Debug) << endl;
        exit(1);
    }
    Log("create shm done", Debug) << "shmid: " << shmid << endl;

    // 3. 建立页表映射(将共享内存挂接到当前进程地址空间)
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if(shmaddr == (char*)-1)
    {
        Log("attch shm fail", Debug) << "shmid: " << shmid << endl;
    }
    Log("attch shm done", Debug) << "shmid: " << shmid << endl;

    // 4. 进程通信
    // 可以将共享内存看成一个大的字符串
    // 注意:共享内存创建好全部都会被置成0
    for(;;)
    {
        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        sleep(1);
    }

    // 5. 将指定的共享内存从自己的进程空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << "shmid: " << shmid << endl; 

    // 6. 删除共享内存,IPC_RMID即便还有进程挂接该共享内存,依旧删除该共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << "shmid: " << shmid << endl; 

    return 0;
}

// shmClient.cc
#include "Comm.hpp"

int main()
{
    // 1. 客户端只需要获取服务端创建的共享内存即可
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if(k == -1)
    {
        Log("create key fail", Error) << "client key: " << TransToHex(k) << endl;
        exit(1);
    }
    Log("create key done", Debug) << "client key: " << TransToHex(k) << endl;


    // 2. 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid == -1)
    {
        Log("create shm fail", Error) << "client key: " << TransToHex(k) << endl;
        exit(2);
    }
    Log("create shm done", Debug) << "client key: " << TransToHex(k) << endl;

    // 3. 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if(shmaddr == (char*)-1)
    {
        Log("attach shm failed", Error) << " client key : " << TransToHex(k) << endl;
        exit(3);
    }
    Log("attach shm success", Debug) << " client key : " << TransToHex(k) << endl;

    // 4. 使用,客户端将共享内存看做一个char类型的buffer
    while(true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
            shmaddr[s - 1] = '\0';  // 清除'\n'
            if(strcmp(shmaddr, "quit") == 0) break;
        }
    }

    // 以下代码是自动向共享内存中写入数据
    // char a = 'a';
    // for(; a <= 'z'; a++)
    // {
    //     // shmaddr[a-'a'] = a;
    //     // 我们是每一次都向shmaddr[共享内存的起始地址]写入
    //     // snprintf(shmaddr, SHM_SIZE - 1, "hello server, 我是其他进程,我的pid: %d, inc: %c\n", getpid(), a);
    //     // sleep(5);
    // }

    // strcpy(shmaddr, "quit");

    // 5. 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm done", Debug) << "client key: " << TransToHex(k) << endl;

    // 客户端不需要删除共享内存!

    return 0;
}

在这里插入图片描述

现象:

  • 就算客户端没有向共享内存中写入数据,服务端也会一直读取。
  • 只要通信双方使用共享内存,一方直接向共享内存中写入数据,另一方就可以马上看到对方写入的数据。共享内存是所有进程间通信(IPC)中速度最快的!因为其不需要过多的拷贝(不需要将数据给操作系统)!!!

在这里插入图片描述
在这里插入图片描述

  • 以共享内存的方式进行进程间通信缺乏访问控制,会带来并发控制!比如:写端还没将全部数据写入,读端就已经开始读取了,这将会带来巨大的问题!

给共享内存添加访问控制

// Comm.hpp
#pragma once

#include <iostream>
#include <cstdio>
#include <cassert>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "Log.hpp"

using namespace std;  // 将std直接展开简答,但不推荐

#define PATH_NAME "/home/Joy"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE:4096KB)的整数倍
#define FIFO_NAME "./fifo"

// 将十进制转为十六进制
string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof buffer, "0x%x", k);
    return buffer;
}

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo(FIFO_NAME, 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success", Notice) << endl;
    }

    ~Init()
    {
        unlink(FIFO_NAME);
        Log("delete fifo success", Notice) << endl;
    }
};

#define READ O_RDONLY
#define WRITE O_WRONLY

int OpenFIFO(std::string pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    assert(fd != -1);
    return fd;
}

// 服务端等待客户端唤醒
void Wait(int fd)
{
    Log("等待中......", Notice) << endl;
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;

}

// 客户端唤醒服务端
void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
    Log("唤醒中......", Notice) << endl;
}

void CloseFIFO(int fd)
{
    close(fd);
}

// shmServer.cc
#include "Comm.hpp"

// 对应的程序在加载的时候,会自动构建全局变量,就要调用该类的构造函数 -- 创建管道文件
// 程序退出的时候,全局变量会被析构,自动调用析构函数,会自动删除管道文件
Init init;  // 管道文件只有在服务端创建即可

int main()
{
    // 1. 创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    assert(k != -1);
    Log("create key done", Debug) << "server key: " << TransToHex(k) << endl;

    // 2. 创建共享内存(建议通信的发起者创建一个全新的共享内存)
    // 创建共享内存是也要指定权限
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        Log("create shm fail", Debug) << endl;
        exit(1);
    }
    Log("create shm done", Debug) << "shmid: " << shmid << endl;

    // 3. 建立页表映射(将共享内存挂接到当前进程地址空间)
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if(shmaddr == (char*)-1)
    {
        Log("attch shm fail", Debug) << "shmid: " << shmid << endl;
    }
    Log("attch shm done", Debug) << "shmid: " << shmid << endl;

    // 4. 进程通信
    // 可以将共享内存看成一个大的字符串
    // 注意:共享内存创建好全部都会被置成0

    // 服务端以读方式打开管道文件
    int fd = OpenFIFO(FIFO_NAME, READ);

    for(;;)
    {
        // 服务端等待客户端唤醒
        Wait(fd);

        printf("%s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        //sleep(1);
    }

    // 5. 将指定的共享内存从自己的进程空间中去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    (void)n;
    Log("detach shm done", Debug) << "shmid: " << shmid << endl; 

    // 6. 删除共享内存,IPC_RMID即便还有进程挂接该共享内存,依旧删除该共享内存
    n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
    Log("delete shm done", Debug) << "shmid: " << shmid << endl;

    CloseFIFO(fd); 

    return 0;
}

// shmClient.cc
#include "Comm.hpp"

int main()
{
    // 1. 客户端只需要获取服务端创建的共享内存即可
    key_t k = ftok(PATH_NAME, PROJ_ID);
    if(k == -1)
    {
        Log("create key fail", Error) << "client key: " << TransToHex(k) << endl;
        exit(1);
    }
    Log("create key done", Debug) << "client key: " << TransToHex(k) << endl;


    // 2. 获取共享内存
    int shmid = shmget(k, SHM_SIZE, 0);
    if(shmid == -1)
    {
        Log("create shm fail", Error) << "client key: " << TransToHex(k) << endl;
        exit(2);
    }
    Log("create shm done", Debug) << "client key: " << TransToHex(k) << endl;

    // 3. 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if(shmaddr == (char*)-1)
    {
        Log("attach shm failed", Error) << " client key : " << TransToHex(k) << endl;
        exit(3);
    }
    Log("attach shm success", Debug) << " client key : " << TransToHex(k) << endl;

    // 客户端以写方式打开管道文件
    int fd = OpenFIFO(FIFO_NAME, WRITE);

    // 4. 使用,客户端将共享内存看做一个char类型的buffer
    while(true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        if(s > 0)
        {
            shmaddr[s - 1] = '\0';  // 清除'\n'
            Signal(fd); // 客户端唤醒服务端
            if(strcmp(shmaddr, "quit") == 0) break;
        }
    }

    // 以下代码是自动向共享内存中写入数据
    // char a = 'a';
    // for(; a <= 'z'; a++)
    // {
    //     // shmaddr[a-'a'] = a;
    //     // 我们是每一次都向shmaddr[共享内存的起始地址]写入
    //     // snprintf(shmaddr, SHM_SIZE - 1, "hello server, 我是其他进程,我的pid: %d, inc: %c\n", getpid(), a);
    //     // sleep(5);
    // }

    // strcpy(shmaddr, "quit");

    // 5. 去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm done", Debug) << "client key: " << TransToHex(k) << endl;

    // 客户端不需要删除共享内存!

    return 0;
}

在这里插入图片描述

在这里插入图片描述
因为管道具有访问控制,我们只要给共享内存加个管道就可以实现进程通信的访问控制了。该管道文件并不是用来通信的,而是用来访问控制的,管道文件内的数据是多少并不重要!

👉system V消息队列(了解)👈

  • 消息队列(先进先出)提供了一个从一个进程向另外一个进程发送一块数据的方法。
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值。
  • IPC 资源必须删除,否则不会自动清除,除非重启,所以 system V IPC 资源的生命周期随内核。
  • 常用系统调用:ftok,msgget(创建消息队列),msgctl(控制消息队列),msgsnd(向消息队列发送数据),msgrcv(从消息队列中读取数据)等。

在这里插入图片描述
在这里插入图片描述
注:共享内存只有在当前映射连接数为 0 时才会被删除释放。

👉system V信号量👈

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

进程互斥

  • 我们之前学习的所有的通信方式,本质都是优先解决一个问题:让不同的进程看到同一个资源!
  • 让不同的进程看到同一个资源,比如共享内存,也带了一些时序问题,造成数据不一致问题。
  • 多个进程(执行流)看到的公共的一份资源,称为临界资源或互斥资源。
  • 进程中访问临界资源的代码,称为临界区。
  • 多个执行流互相运行时互相干扰,是因为我们不加保护地访问了同样的资源(临界资源)。在非临界区,多个执行流不会互相干扰。
  • 为了更好地进行临界区的保护,可以让多执行流在任何时候都只有一个进程进入临界区,这种特征称为互斥。
  • 原子性是指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行,没有任何其他的中间状态。

通过看电影买票的例子来理解信号量

在这里插入图片描述
在这里插入图片描述

信号量是对临界资源的预定机制!!!

👉总结👈

本篇博客主要讲解了什么是共享内存、共享内存的原理、用共享内存实现客户端和服务端的通信、什么是消息队列、消息量以及进程互斥等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

go进阶(2) -深入理解Channel实现原理

Go的并发模型已经在https://guisu.blog.csdn.net/article/details/129107148 详细说明。 1、channel使用详解 1、channel概述 Go的CSP并发模型&#xff0c;是通过goroutine和channel来实现的。 channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲&#xf…

通用信息抽取技术UIE产业案例解析,Prompt 范式落地经验分享!

想了解用户的评价究竟是“真心夸赞”还是“阴阳怪气”&#xff1f;想快速从多角色多事件的繁杂信息中剥茧抽丝提取核心内容&#xff1f;想通过聚合相似事件准确地归纳出特征标签&#xff1f;……想了解UIE技术在产业中的实战落地经验&#xff1f;通用信息抽取技术 UIE 产业案例…

FPGA基础知识

FPGA是在PAL、PLA和CPLD等可编程器件的基础上进一步发展起来的一种更复杂的可编程逻辑器件。它是ASIC领域中的一种半定制电路&#xff0c;既解决了定制电路的不足&#xff0c;又克服了原有可编程器件门电路有限的缺点。 由于FPGA需要被反复烧写&#xff0c;它实现组合逻辑的基…

【强化学习】强化学习数学基础:贝尔曼公式

强化学习数学基础&#xff1a;贝尔曼公式强化学习的数学原理课程总览贝尔曼公式&#xff08;Bellman Equation&#xff09;一个示例状态值贝尔曼公式&#xff1a;推导过程贝尔曼公式&#xff1a;矩阵-向量形式&#xff08;Matrix-vector form&#xff09;贝尔曼公式&#xff1a…

(四)应变度量

本文主要内容包括&#xff1a;1. Hill 应变度量 与 Seth 应变度量2. Hill -Seth 应变度量的 Lagrange 描述2.1. Green-Lagrange 应变张量2.2. 物质 Biot 应变张量/工程应变2.3. 右 Henkey 应变张量/Lagrange 型对数应变2.4. Piola 应变张量3. Hill -Seth 应变度量的 Euler 描述…

Tesla都使用什么编程语言?

作者 | 初光 出品 | 车端 备注 | 转载请阅读文中版权声明 知圈 | 进“汽车电子与AutoSAR开发”群&#xff0c;请加微“cloud2sunshine” 总目录链接>> AutoSAR入门和实战系列总目录 带着对更美好未来的愿景&#xff0c;特斯拉不仅成为有史以来最有价值的汽车公司&…

乐友商城学习笔记(五)

什么是Nginx Nginx是一个高性能的web和反向代理服务器 作为web服务器作为负载均衡服务器作为邮件代理服务 树组件的用法 跨域问题 跨域&#xff1a;浏览器对javastript的同源策略的限制。 以下情况都属于跨域&#xff1a; 域名不同域名相同&#xff0c;端口不同二级域名不…

Python每日一练(20230225)

目录 1. 整数反转 2. 求最大公约数和最小公倍数 最大公约数 最小公倍数 3. 单词搜索 II 附录&#xff1a; DFS 深度优先搜索算法 BFS 广度优先搜索算法 BFS 和 DFS 的区别 1. 整数反转 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。…

大型旋转设备滑动轴承X、Y测点振动值说明(转载的)

滑动轴承支撑的大型旋转设备&#xff0c;绝大部分的故障都表现为不平衡引起的1倍频振动&#xff0c;诊断故障原因要根据振动随转速、负荷、温度、时间的变化情况来具体判断。滑动轴承设备的诊断主要依据电涡流传感器测量轴和轴瓦间的相对振动&#xff0c;判断转子相关的各种问题…

基于SpringBoot的共享汽车管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

Orin安装ssh、vnc教程

文章目录一&#xff1a;ssh远程终端的配置PC的配置MobaXterm的下载二&#xff1a;VNC Viewer远程图形界面终端配置&#xff1a;PC配置&#xff1a;一&#xff1a;ssh远程 终端的配置 1.ifconfig查看终端ip地址 其中的eth是网口&#xff0c;我们需要看的是wlan0下的inet&#…

5M1270ZT144A5N CPLD 980MC 6.2NS 144TQFP /5M1270ZT144C5N

【产品介绍】MAX V系列低成本和低功耗CPLD提供更大的密度和每占地面积的I/O。MAX V器件的密度从40到2210个逻辑元件(32到1700个等效宏单元)和多达271个I/O&#xff0c;为I/O扩展、总线和协议桥接、电源监控和控制、FPGA配置和模拟IC接口等应用提供可编程解决方案。MAX V器件具有…

MYSQL 索引失效的十个场景(二)

六、对索引列运算&#xff08;如&#xff0c;、-、*、/、%等&#xff09;&#xff0c;索引失效 CREATE TABLE student (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,score decimal(10,2) DEFAULT NULL,subject varchar(…

【华为OD机试模拟题】用 C++ 实现 - 绘图机器(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

【Leedcode】环形链表必备的面试题和证明题(附图解)

环形链表必备的面试题和证明题&#xff08;附图解&#xff09; 文章目录环形链表必备的面试题和证明题&#xff08;附图解&#xff09;前言一、第一题1.题目2.思路3.代码4.延伸问题(1)证明题一&#xff1a;(2)证明题二&#xff1a;二、第二题1.题目2.思路延伸的证明题总结前言 …

【网络原理8】HTTP请求篇

在上一篇文章当中&#xff0c;我们也提到了什么是HTTP。 每一个HTTP请求&#xff0c;都会对应一个HTTP响应。 下面这一篇文章&#xff0c;将聊一下HTTP请求的一些内容 目录 一、URL 第一部分&#xff1a;协议名称 第二部分:认证信息(新的版本已经没有了) 第三部分&#xf…

这款 Python 调试神器推荐收藏

大家好&#xff0c;对于每个程序开发者来说&#xff0c;调试几乎是必备技能。 代码写到一半卡住了&#xff0c;不知道这个函数执行完的返回结果是怎样的&#xff1f;调试一下看看 代码运行到一半报错了&#xff0c;什么情况&#xff1f;怎么跟预期的不一样&#xff1f;调试一…

【教学类-10-03】python单线程下载哔哩哔哩网址(中间字母不同,前面后面相同)的视频

背景需求&#xff1a;最近测试以前的多线程&#xff08;同时下载5个视频&#xff09;&#xff0c;结果30个视频只下到了3个&#xff0c;于是把“单个下载&#xff08;单线程下载&#xff09;”的一个代码进行拓展研究。前一篇介绍了网址尾数递增的遍历程序&#xff0c;本篇介绍…

【华为OD机试模拟题】用 C++ 实现 - 最大报酬(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

数据结构与算法之[把数字翻译成字符串]动态规划

前言&#xff1a;最近在刷动态规划的算法题目&#xff0c;感觉这一类题目还是有一点难度的&#xff0c;但是不放弃也还是能学好的&#xff0c;今天给大家分享的是牛客网中的编程题目[把数字翻译成字符串]&#xff0c;这是一道经典的面试题目&#xff0c;快手&#xff0c;字节跳…