高性能编程:无锁队列----MsgQueue代码实践

news2024/11/15 10:20:01

目录

概述

代码结构

1. 头文件解析 (msgqueue.h)

2. 实现文件解析 (msgqueue.c)

核心功能解析

2.1 创建队列 (msgqueue_create)

2.2 放入消息 (msgqueue_put)

2.3 获取消息 (msgqueue_get)

2.4 交换队列 (__msgqueue_swap)

2.5 阻塞与非阻塞模式

2.6 销毁队列 (msgqueue_destroy)

3. 测试程序解析 (main_msgqueue.cpp)

关键点解析

4. 工作机制详解

4.1 Put队列和Get队列的分离

4.2 队列为空时的阻塞与交换

4.3 生产者与消费者的碰撞处理

4.4 阻塞与非阻塞模式

5. 示例程序运行流程

6. 运行示例

总结


高性能编程:无锁队列概念icon-default.png?t=O83Ahttps://blog.csdn.net/weixin_43925427/article/details/142203825?sharetype=blogdetail&sharerId=142203825&sharerefer=PC&sharesource=weixin_43925427&sharefrom=from_linkMsgQueue在多生产者多消费者(MPMC)场景下的实现原理及其工作机制。

概述

msgqueue是一个用于多生产者多消费者场景的消息队列实现。它将队列分为put队列get队列,分别用于生产者和消费者操作。其主要逻辑如下:

  1. Put队列:生产者线程将消息入队到put队列。
  2. Get队列:消费者线程从get队列中出队消息。
  3. 交换机制:当get队列为空时,尝试与put队列进行交换,使消费者能够获取新的消息。
  4. 阻塞与非阻塞:根据队列的状态和配置,决定是否阻塞消费者或生产者线程。

代码结构

代码分为三个主要部分:

  1. 头文件 (msgqueue.h):定义了消息队列的接口和数据结构。
  2. 实现文件 (msgqueue.c):实现了消息队列的功能。
  3. 测试程序 (main_msgqueue.cpp):演示了如何使用消息队列。

1. 头文件解析 (msgqueue.h)

#ifndef _MSGQUEUE_H_
#define _MSGQUEUE_H_

#include <stddef.h>

typedef struct __msgqueue msgqueue_t;

#ifdef __cplusplus
extern "C"
{
#endif

/* 消息队列的接口函数 */
msgqueue_t *msgqueue_create(size_t maxlen, int linkoff);
void msgqueue_put(void *msg, msgqueue_t *queue);
void *msgqueue_get(msgqueue_t *queue);
void msgqueue_set_nonblock(msgqueue_t *queue);
void msgqueue_set_block(msgqueue_t *queue);
void msgqueue_destroy(msgqueue_t *queue);

#ifdef __cplusplus
}
#endif

#endif
  • msgqueue_create:创建一个消息队列,maxlen表示队列的最大长度,linkoff表示消息结构中用于链接下一个消息的偏移量。
  • msgqueue_put:生产者将消息放入队列。
  • msgqueue_get:消费者从队列中获取消息。
  • msgqueue_set_nonblock / msgqueue_set_block:设置队列为非阻塞或阻塞模式。
  • msgqueue_destroy:销毁消息队列,释放资源。

2. 实现文件解析 (msgqueue.c)

#include <errno.h>
#include <stdlib.h>
#include <pthread.h>
#include "msgqueue.h"

/* 消息队列的内部结构 */
struct __msgqueue
{
    size_t msg_max;          // 队列的最大长度
    size_t msg_cnt;          // 当前消息数量
    int linkoff;             // 链接偏移量
    int nonblock;            // 是否为非阻塞模式
    void *head1;             // get队列的第一个头节点
    void *head2;             // put队列的第一个头节点
    void **get_head;         // 指向get队列头指针的指针
    void **put_head;         // 指向put队列头指针的指针
    void **put_tail;         // 指向put队列尾指针的指针
    pthread_mutex_t get_mutex; // get队列的互斥锁
    pthread_mutex_t put_mutex; // put队列的互斥锁
    pthread_cond_t get_cond;   // get队列的条件变量
    pthread_cond_t put_cond;   // put队列的条件变量
};
核心功能解析
2.1 创建队列 (msgqueue_create)
msgqueue_t *msgqueue_create(size_t maxlen, int linkoff)
{
    msgqueue_t *queue = (msgqueue_t *)malloc(sizeof(msgqueue_t));
    int ret;

    if (!queue)
        return NULL;

    ret = pthread_mutex_init(&queue->get_mutex, NULL);
    if (ret == 0)
    {
        ret = pthread_mutex_init(&queue->put_mutex, NULL);
        if (ret == 0)
        {
            ret = pthread_cond_init(&queue->get_cond, NULL);
            if (ret == 0)
            {
                ret = pthread_cond_init(&queue->put_cond, NULL);
                if (ret == 0)
                {
                    queue->msg_max = maxlen;
                    queue->linkoff = linkoff;
                    queue->head1 = NULL;
                    queue->head2 = NULL;
                    queue->get_head = &queue->head1;
                    queue->put_head = &queue->head2;
                    queue->put_tail = &queue->head2;
                    queue->msg_cnt = 0;
                    queue->nonblock = 0;
                    return queue;
                }

                pthread_cond_destroy(&queue->get_cond);
            }

            pthread_mutex_destroy(&queue->put_mutex);
        }

        pthread_mutex_destroy(&queue->get_mutex);
    }

    errno = ret;
    free(queue);
    return NULL;
}
  • 初始化互斥锁和条件变量:为getput操作分别初始化互斥锁和条件变量。
  • 初始化队列头指针get_head指向head1put_headput_tail指向head2,实现putget队列的分离。
2.2 放入消息 (msgqueue_put)
void msgqueue_put(void *msg, msgqueue_t *queue)
{
    void **link = (void **)((char *)msg + queue->linkoff);

    *link = NULL;
    pthread_mutex_lock(&queue->put_mutex);
    while (queue->msg_cnt > queue->msg_max - 1 && !queue->nonblock)
        pthread_cond_wait(&queue->put_cond, &queue->put_mutex);

    *queue->put_tail = link;
    queue->put_tail = link;
    queue->msg_cnt++;
    pthread_mutex_unlock(&queue->put_mutex);
    pthread_cond_signal(&queue->get_cond);
}
  • 计算消息的链接指针:根据linkoff偏移量获取消息结构中用于链接下一个消息的指针。
  • 设置链接指针为空:表示当前消息是链表的末尾。
  • 锁定put队列:确保多个生产者线程安全地操作put队列。
  • 阻塞条件:如果当前消息数量超过msg_max - 1且不是非阻塞模式,生产者线程将被阻塞,直到有空间可用。
  • 入队操作
    • 将当前消息链接到put队列的尾部。
    • 更新put队列的尾指针。
    • 增加消息计数。
  • 解锁put队列:释放锁,允许其他生产者继续入队。
  • 通知消费者:通过条件变量get_cond唤醒可能被阻塞的消费者线程。
2.3 获取消息 (msgqueue_get)
void *msgqueue_get(msgqueue_t *queue)
{
    void *msg;

    pthread_mutex_lock(&queue->get_mutex);
    if (*queue->get_head || __msgqueue_swap(queue) > 0)
    {
        msg = (char *)*queue->get_head - queue->linkoff;
        *queue->get_head = *(void **)*queue->get_head;
    }
    else
        msg = NULL;

    pthread_mutex_unlock(&queue->get_mutex);
    return msg;
}
  • 锁定get队列:确保多个消费者线程安全地操作get队列。
  • 判断get队列是否有消息
    • 如果*get_head不为空,直接获取消息。
    • 否则,调用__msgqueue_swap尝试将put队列和get队列交换。
  • 获取消息
    • 计算消息的实际地址(通过减去linkoff偏移量)。
    • 更新get队列的头指针,指向下一个消息。
  • 解锁get队列:释放锁,允许其他消费者继续出队。
  • 返回消息:如果队列为空,返回NULL
2.4 交换队列 (__msgqueue_swap)
static size_t __msgqueue_swap(msgqueue_t *queue)
{
    void **get_head = queue->get_head;
    size_t cnt;

    queue->get_head = queue->put_head;
    pthread_mutex_lock(&queue->put_mutex);
    while (queue->msg_cnt == 0 && !queue->nonblock)
        pthread_cond_wait(&queue->get_cond, &queue->put_mutex);

    cnt = queue->msg_cnt;
    if (cnt > queue->msg_max - 1)
        pthread_cond_broadcast(&queue->put_cond);

    queue->put_head = get_head;
    queue->put_tail = get_head;
    queue->msg_cnt = 0;
    pthread_mutex_unlock(&queue->put_mutex);
    return cnt;
}
  • 交换get_head与put_head:将put队列的头指针赋值给get队列,使消费者能够获取新的消息。
  • 锁定put队列:在交换过程中,确保生产者不会同时操作put队列。
  • 阻塞条件:如果put队列为空且不是非阻塞模式,消费者线程将被阻塞,直到有新的消息入队。
  • 获取消息计数:记录当前put队列中的消息数量。
  • 条件广播:如果消息数量超过msg_max - 1,通过put_cond唤醒被阻塞的生产者线程。
  • 重置put队列
    • 将put队列的头尾指针指向之前的get_head。
    • 重置消息计数。
  • 解锁put队列:允许生产者线程继续入队。
  • 返回消息计数:用于判断是否有新消息可供获取。
2.5 阻塞与非阻塞模式
void msgqueue_set_nonblock(msgqueue_t *queue)
{
    queue->nonblock = 1;
    pthread_mutex_lock(&queue->put_mutex);
    pthread_cond_signal(&queue->get_cond);
    pthread_cond_broadcast(&queue->put_cond);
    pthread_mutex_unlock(&queue->put_mutex);
}

void msgqueue_set_block(msgqueue_t *queue)
{
    queue->nonblock = 0;
}
  • msgqueue_set_nonblock
    • 设置队列为非阻塞模式。
    • 通过条件变量唤醒所有被阻塞的生产者和消费者线程,防止死锁。
  • msgqueue_set_block
    • 设置队列为阻塞模式。
2.6 销毁队列 (msgqueue_destroy)
void msgqueue_destroy(msgqueue_t *queue)
{
    pthread_cond_destroy(&queue->put_cond);
    pthread_cond_destroy(&queue->get_cond);
    pthread_mutex_destroy(&queue->put_mutex);
    pthread_mutex_destroy(&queue->get_mutex);
    free(queue);
}
  • 销毁互斥锁和条件变量:释放资源。
  • 释放队列内存:销毁队列对象。

3. 测试程序解析 (main_msgqueue.cpp)

#include "msgqueue.h"

#include <cstddef>
#include <thread>
#include <iostream>

// 消息结构体
struct Count {
    Count(int _v) : v(_v), next(nullptr) {}
    int v;
    Count *next;
};

int main() {
    // linkoff: Count结构体中用于链接下一个节点的指针的偏移量
    msgqueue_t* queue = msgqueue_create(1024, sizeof(int));

    // 生产者线程1
    std::thread pd1([&]() {
        msgqueue_put(new Count(100), queue);
        msgqueue_put(new Count(200), queue);
        msgqueue_put(new Count(300), queue);
        msgqueue_put(new Count(400), queue);
    });

    // 生产者线程2
    std::thread pd2([&]() {
        msgqueue_put(new Count(500), queue);
        msgqueue_put(new Count(600), queue);
        msgqueue_put(new Count(700), queue);
        msgqueue_put(new Count(800), queue);
    });

    // 消费者线程1
    std::thread cs1([&]() {
        Count *cnt;
        while((cnt = (Count *)msgqueue_get(queue)) != NULL) {
            std::cout << std::this_thread::get_id() << " : pop " << cnt->v << std::endl;
            delete cnt;
        }
    });

    // 消费者线程2
    std::thread cs2([&]() {
        Count *cnt;
        while((cnt = (Count *)msgqueue_get(queue)) != NULL) {
            std::cout << std::this_thread::get_id() << " : pop " << cnt->v << std::endl;
            delete cnt;
        }
    });

    // 等待所有线程完成
    pd1.join();
    pd2.join();
    cs1.join();
    cs2.join();

    // 销毁队列
    msgqueue_destroy(queue);
    
    return 0;
}
关键点解析
  1. 消息结构体 Count

    • 包含一个整数值 v 和一个指针 next,用于链接下一个消息。
    • linkoff设置为sizeof(int),表示next指针在结构体中的偏移量。
  2. 创建消息队列

    msgqueue_t* queue = msgqueue_create(1024, sizeof(int));
    
    • maxlen设置为1024,表示队列的最大长度。

    • linkoff设置为sizeof(int),表示消息结构体中next指针的偏移量。

  3. 生产者线程

    • 两个生产者线程pd1pd2,分别放入四个不同的Count消息。

  4. 消费者线程
    • 两个消费者线程cs1cs2,不断从队列中获取消息并打印,然后删除消息。
    • 当队列为空且所有生产者已完成入队,msgqueue_get返回NULL,线程结束。
  5. 销毁队列
    msgqueue_destroy(queue);
    

4. 工作机制详解

msgqueue的工作机制可以概括为以下几个步骤:

4.1 Put队列和Get队列的分离
  • Put队列

    • 由生产者线程操作。
    • 通过put_headput_tail指针管理,生产者将消息链接到put队列的尾部。
    • 使用put_mutexput_cond确保线程安全和同步。
  • Get队列

    • 由消费者线程操作。
    • 通过get_head指针管理,消费者从get队列的头部获取消息。
    • 使用get_mutexget_cond确保线程安全和同步。
4.2 队列为空时的阻塞与交换
  • 当Put队列和Get队列都为空时

    • 消费者线程会被阻塞,等待生产者线程入队新消息。
    • 这是通过__msgqueue_swap函数中的pthread_cond_wait实现的。
  • 当Get队列为空但Put队列有消息时

    • 消费者线程尝试将put队列与get队列交换,使消费者能够获取新的消息。
    • 这是通过__msgqueue_swap函数实现的,锁定put队列并交换头指针。
    • 交换过程中,生产者和消费者可能会发生碰撞,即生产者正在入队时,消费者尝试交换队列。
    • 其他情况下,生产者仅与生产者竞争put队列,消费者仅与消费者竞争get队列,避免了交叉竞争。
4.3 生产者与消费者的碰撞处理
  • 碰撞情形

    • 当一个消费者尝试交换队列时,可能会与多个生产者线程同时操作put队列。
    • 为了保证线程安全,__msgqueue_swap在交换过程中锁定了put队列的互斥锁,确保只有一个消费者能够成功交换。
  • 非碰撞情形

    • 生产者仅操作put队列,锁定put_mutex,避免与其他生产者竞争。
    • 消费者仅操作get队列,锁定get_mutex,避免与其他消费者竞争。
4.4 阻塞与非阻塞模式
  • 阻塞模式

    • 当队列满时,生产者线程会被阻塞,等待消费者线程出队腾出空间。
    • 当队列空时,消费者线程会被阻塞,等待生产者线程入队新消息。
  • 非阻塞模式

    • 通过调用msgqueue_set_nonblock将队列设置为非阻塞模式。
    • 在非阻塞模式下,生产者和消费者线程不会因为队列满或空而被阻塞,而是立即返回。

5. 示例程序运行流程

  1. 创建队列

    • 队列初始为空,head1head2均为NULL
    • get_head指向head1put_headput_tail指向head2
  2. 生产者入队

    • pd1pd2分别向put队列中入队4个消息。
    • 每次msgqueue_put操作:
      • 计算消息的链接指针。
      • 锁定put_mutex
      • 检查队列是否已满,必要时阻塞。
      • 将消息链接到put队列的尾部。
      • 更新尾指针和消息计数。
      • 解锁put_mutex并通知消费者。
  3. 消费者出队

    • cs1cs2不断调用msgqueue_get获取消息。
    • 每次msgqueue_get操作:
      • 锁定get_mutex
      • 检查get队列是否有消息,若无则尝试交换。
      • 获取消息并更新get队列的头指针。
      • 解锁get_mutex并返回消息。
      • 打印消息内容并删除消息对象。
  4. 交换队列

    • 当get队列为空且put队列有消息时,消费者线程调用__msgqueue_swap将put队列与get队列交换。
    • 这样,消费者可以从新的get队列中获取到生产者入队的消息。
  5. 线程同步

    • 生产者和消费者通过互斥锁和条件变量确保线程安全和同步。
    • 队列的阻塞与非阻塞模式根据具体需求进行配置。

6. 运行示例

输出如下:

140353163797760 : pop 100
140353155405056 : pop 500
140353163797760 : pop 200
140353155405056 : pop 600
140353163797760 : pop 300
140353155405056 : pop 700
140353163797760 : pop 400
140353155405056 : pop 800
  • 每一行显示了哪个消费者线程(通过线程ID标识)获取了哪个消息。
  • 消费者线程交替获取生产者线程入队的消息,确保了队列的先进先出(FIFO)特性。

总结

msgqueue通过将队列分为put和get两部分,并使用互斥锁和条件变量实现了多生产者多消费者的线程安全。然而,它仍依赖于传统的锁机制,而非真正的无锁操作。如果对性能有更高的要求,建议探索无锁队列的实现方法,如基于原子操作的Michael & Scott队列。此外,结合内存管理优化和缓存友好性设计,可以进一步提升队列的性能和效率。

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

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

相关文章

GPT Prompt

Reference https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-apihttps://platform.openai.com/docs/guides/prompt-engineeringbilibili 8分钟系统学习提示工程,别再说大模型还不够聪明!Prompt Engineering,提示词,Few…

深兰科技董事长陈海波应出席“香港大学国际科创大赛”

近日&#xff0c;以“人工智能与智能制造”为主题的“香港大学国际科创大赛——知识转化论坛(沪港场)”在上海市普陀区隆重举行&#xff0c;众多来自人工智能与智能制造领域的专家学者与企业高管齐聚一堂&#xff0c;共话人工智能、智能制造与新质生产力的深度融合、最新进展以…

第 11篇 Helm 部署 RabbitMQ

文章目录 RabbitMQ 简介Helm ChartChart 版本选择自定义 values.yaml部署效果 参考相关博文 &#x1f680; 本文内容&#xff1a;在 Helm 中部署 RabbitMQ。 RabbitMQ 简介 ⭐ RabbitMQ&#xff1a;开源、通用消息代理&#xff0c;为一致性、高可用消息场景设计&#xff0c;包…

想引领潮流,得保持动销方案创新!

在竞争激烈的市场中&#xff0c;动销方案创新至关重要。 创新动销方案能提升品牌知名度与美誉度&#xff0c;吸引消费者关注&#xff0c;提高曝光度&#xff1b;还可增加销售额与市场份额&#xff0c;激发购买兴趣&#xff1b;更能增强企业竞争力&#xff0c;在同质化竞争中脱…

TiDB 扩容过程中 PD 生成调度的原理及常见问题丨TiDB 扩缩容指南(一)

导读 作为一个分布式数据库&#xff0c;扩缩容是 TiDB 集群最常见的运维操作之一。本系列文章&#xff0c;我们将基于 v7.5.0 具体介绍扩缩容操作的具体原理、相关配置及常见问题的排查。 通常&#xff0c;我们根据当前资源状态来决定是否需要调整 TiKV 节点的规模&#xff0…

Nacos 2.x为什么新增了RPC的通信方式?

Nacos 2.X 在 1.X 的架构基础上&#xff0c;通信层通过 gRPC 和 Rsocket 实现了长连接 RPC 调用和推送能力。主要是为了改善 Nacos 在大规模集群环境下的性能和稳定性。 同时新增一个链接层&#xff0c;用来将不同类型 Request 请求&#xff0c;将来自不同客户端的不同类型请求…

JVM面试真题总结(九)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 描述CMS垃圾收集的工作过程 CMS&#xff08;Concurrent Mark Swee…

代码随想录训练营 Day60打卡 图论part10 SPFA算法 Bellman-Ford 之判断负权回路 Bellman-Ford 之单源有限最短路

代码随想录训练营 Day60打卡 图论part10 一、Bellman_ford 队列优化算法&#xff08;又名SPFA&#xff09; 例题&#xff1a;卡码94. 城市间货物运输 I 题目描述 某国为促进城市间经济交流&#xff0c;决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市&#xff0c;通过…

Monster Sound FX Pack 2 怪物恶魔野兽声效包

这是受最新电影和游戏启发而创作的一系列超凡怪兽音效中的第二卷。Monster Sound FX Pack 2 包含精心设计的声音,充满个性,为听众带来新的体验。这些声音经过专业设计,是严肃的声音设计师和游戏开发者的必备品! 发现声音非常适合龙、野兽、外星人、兽人、地精、巨人、巨魔、…

【Qt笔记】QScrollArea控件详解

目录 引言 一、QScrollArea 的基本概念 二、QScrollArea 的主要属性 2.1 设置内容大小是否随滚动区域变化 2.2 设置水平与垂直滚动条 2.3 设置视口外边距 三、QScrollArea 的常用方法 3.1 设置显示小部件 3.2 返回当前设置的小部件 3.3 设置内部小部件是否可以填充…

基于51单片机的16X16点阵显示屏proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1JQ225NSKweqf1Zlad_f1Mw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

ZW3D二次开发_UI_非模板表单_设置表单显示位置

1.ZW3D弹出非模板表单时可以设置弹出位置&#xff08;居中、左下角、右上角等&#xff09; 2.假设已创建好非模板表单 3.在Form属性中添加form_pos属性 4.输入值 base,CTR,0.0 &#xff0c;如下图 也可以设置为其他值显示在不同的位置&#xff0c;如下 5.重新编译&#xff0c;…

宏电5G工业互联网解决方案荣获第七届“绽放杯”5G应用征集大赛5G应用融合产品专题赛优秀奖。

近日&#xff0c;“宏电5G工业互联网解决方案赋能传统企业数字化转型升级”项目荣获第七届“绽放杯”5G应用征集大赛5G应用融合产品专题赛优秀奖。 “绽放杯”由中国信息通信研究院及三大运营商共同主办&#xff0c;是国内最具影响力的全国性5G应用赛事&#xff0c;本次“绽放…

大数据-136 - ClickHouse 集群 表引擎详解1 - 日志、Log、Memory、Merge

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

《深入理解 Java 中的多线程基础(篇一)》

多线程基础 概述 现代操作系统&#xff08;Windows&#xff0c;macOS&#xff0c;Linux&#xff09;都可以执行多任务。多任务就是同时运行多个任务。 例如&#xff1a;播放音乐的同时&#xff0c;浏览器可以进行文件下载&#xff0c;同时可以进行QQ消息的收发。 CPU执行代码…

发展与监管协同发力 人工智能算法领域已形成良好生态

发展与监管协同发力 人工智能算法领域已形成良好生态 近日&#xff0c;全国组织机构统一社会信用代码数据服务中心对国家网信办公示的人工智能领域备案信息进行了详尽的分析&#xff0c;揭示了我国人工智能产业的蓬勃景象。据统计&#xff0c;我国人工智能领域的备案主体遍布各…

uni-app生命周期(三)

文章目录 一、uni-app的生命周期二、应用生命周期三、页面的生命周期函数1.简介2.页面加载时序介绍3.页面加载常见问题4.页面加载顺序4.部分生命周期介绍 四、组件的生命周期函数 一、uni-app的生命周期 应用生命周期&#xff08;整个App的生命周期&#xff09; 在app.vue里面…

认知杂谈61《天呐!“稳住,别浪” 竟藏着改变人生的惊天秘密,不看后悔一辈子!》

内容摘要&#xff1a; 生活要耐心&#xff0c;记住 “稳住&#xff0c;别浪”。遇难关别慌&#xff0c;如考教资、学平面设计等&#xff0c;一步步来。保护名声与人脉&#xff0c;多交靠谱朋友。越乱越要静&#xff0c;困难时冷静分析。培养耐心&#xff0c;认识天性、明确预算…

多个系统运维压力大?统一运维管理为IT轻松解忧

企业基于网络安全管理需要&#xff0c;采用防火墙、网闸、云桌面等多种方式进行网络隔离&#xff0c;网络隔离后&#xff0c;数据仍需在不同网络区域间流转&#xff0c;此时便产生了网间数据摆渡需求。为了业务有序正常开展&#xff0c;同时保证严密的网络隔离架构不受破坏&…

云曦2024秋季学期开学考复现

Web 学习高数 资料&#xff1a;命令执行中关于PHP正则表达式的一些绕过方法_正则表达式中过滤的怎么绕过-CSDN博客 记 [CISCN 2019 初赛]Love Math三种解法-CSDN博客 WEB攻防-RCE代码&命令执行&过滤绕过&异或无字符&无回显方案&黑白盒挖掘_代码执行 异…