当前位置: 首页 > news >正文

并发编程(14)——内存栅栏

文章目录

  • 十四、day14
  • 1. 内存栅栏
    • 1.1 什么是栅栏
    • 1.2 栅栏和原子操作的对比
      • 1.2.1 获取操作
      • 1.2.2 释放操作
    • 1.3 线程可见顺序
    • 1.4 通过栅栏保证指令编排顺序
    • 1.5 通过栅栏令非原子操作服从内存次序
    • 1.6 同步线程间的内存访问

十四、day14

在学习完内存模型、内存序、原子类型、操作的相关内容后,并通过Acquire-Release模型和原子操作实现了无锁环形并发队列,今天学习关于内存栅栏的内容。

参考:

恋恋风辰官方博客

C++ 内存模型

Introduction | Concurrency-with-Modern-C++

C++编程:内存栅栏(Memory Barrier)详解及在多线程编程中的应用-CSDN博客


1. 内存栅栏

1.1 什么是栅栏

栅栏主要用于强制施加内存次序,却无须更改任何数据,通常于服从 memory_order_relaxed 次序的原子操作组合使用。用大白话来说,栅栏用于阻止编译器或CPU对某些内存操作进行重排,确保在它之前的操作完成后,才会执行它之后的操作,从而维护内存操作的顺序一致性。因为 memory_order_relaxed 是最宽松的内存序,它只能保证原子性,却不能保证多线程之间的先行性和顺序性,所以服从 memory_order_relaxed 次序的原子操作通常会通过编译器进行指令重排,而栅栏会限制这种重排。

我们之前学习了互斥量 mutex:拿到mutex锁的线程将拥有唯一进入临界区的资格(共享互斥除外)。其实 mutex 的加锁和解锁之间也起到了”栅栏“的作用,因为栅栏中的代码不会被编译器重排到栅栏之外(但不保证栅栏之外的内容进入栅栏之中)。

如下图的三种情况,第一种可能会被优化成第二种。但是第二种情况不会被优化成第三种:

在这里插入图片描述


通常,栅栏有三种:

  • 全栅(full fence):指定服从memory_order_seq_cst或者memory_order_acq_rel。在任意两个操作(读和写)之间使用完整的栅栏std::atomic_thread_fence(),可以避免这些操作的重新排序。不过,对于存储-加载操作来说,它们可能会被重新排序。
  • 获取栅栏(acquire fence):指定服从memory_order_acquire。避免在获取栅栏之前的读操作,被获取栅栏之后的读或写操作重新排序。
  • 释放栅栏(release fence)。指定服从memory_order_release。避免释放栅栏之后的写操作,在释放栅栏之前通过读或写操作重新排序。

获取是一个加载操作, 释放是一个存储操作。如果在加载和存储操作的四种组合之间,放一个内存屏障中会发生什么情况呢?

  • ① Load-Load:读接着读
  • ② Load-Store:先读后写
  • ③ Store-Load:先写后读
  • ④ Store-Store:写接着写

全栅可以防止①②④三种情况下的指令重排,但不能防止③被重排

那么,哪些操作可以翻过栅栏,通过下面的图例进行解释,其中红色的斜杆表示这种类型的乱序会被禁止。

  1. 全栅

在这里插入图片描述

也可以显式地调用std::atomic_thread_fence(std::memory_order_seq_cst),而不是std::atomic_thread_fence()。默认情况下,栅栏使用内存序为顺序一致性。如果对全栅使用顺序一致性,那么std::atomic_thread_fence也将遵循全局序。

  1. 获取栅栏

在这里插入图片描述

acquire fence 阻止了所有在它之前的读操作与在它之后的读写操作乱序,保证栅栏之后的读取操作会看到栅栏之前的操作的影响

  1. 释放栅栏
    在这里插入图片描述

release fence 阻止了所有在它之前的读写操作与在它之后的写操作乱序

三种类型的fence均不会禁止先写后读的乱序。

从C++11开始,提供了下面两种栅栏类型:

  • std::atomic_thread_fence : 同步线程间的内存访问。
  • std::atomic_signal_fence : 线程内信号之间的同步。

我们一般使用后者多一些。

1.2 栅栏和原子操作的对比

获取-释放栅栏与原子获取-释放内存序有着相似的同步方式和顺序,但二者有以下两种区别:

  • 栅栏不需要原子操作
  • 获取-释放栅栏比原子操作更重量级

获取-释放操作的主要思想是,在线程间建立同步和排序约束,这些同步和顺序约束也适用于使用宽松次序的原子操作或非原子操作。注意,获取-释放操作是成对出现的。此外,对获取-释放语义的原子变量的操作,必须作用在相同的原子变量上。不过,我们现在先将这些操作分开来看。

1.2.1 获取操作

在原子变量(内存序为std::memory_order_acquire)上进行的加载 (读取)操作是一个获取操作,确保在此之后的所有读操作只会在此读操作完成之后进行
在这里插入图片描述

std::atomic_thread_fence内存序设置为std::memory_order_acquire,这对内存访问重排添加了更严格的约束:

在这里插入图片描述

1.2.2 释放操作

对内存序为 std::memory_order_release 的原子变量,进行存储(写)操作时,这些操作属于释放操作。确保在此之前的所有写操作都在此写操作完成前对其他线程可见

在这里插入图片描述

释放栅栏为

在这里插入图片描述

1.3 线程可见顺序

在六种内存序和三种内存模型中,只有 memory_order_seq_cst 以及其实现的 Sequencial consistent 模型能够保证原子变量修改的值在其他多线程中看到的顺序是一致的。但我们可以通过同步机制保证一个线程对原子变量的修改对另一个线程可见。通过“Syncronizes With” 的方式达到先行的效果。

如果我们线程1对原子变量 A 的store操作采用release内存序,而线程2对原子变量 B 的load采用acquire内存序,并不能保证变量A 的操作一定比 变量B的操作先执行。因为两个线程并行执行无法确定先后顺序,我们指的先行不过是说如果B读取了A操作的结果,则B依赖于A,则称A先行于B。

比如:

#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> x, y;
std::atomic<int> z;
void write_x()
{x.store(true, std::memory_order_release); //1
}
void write_y()
{y.store(true, std::memory_order_release); //2
}
void read_x_then_y()
{while (!x.load(std::memory_order_acquire));if (y.load(std::memory_order_acquire))   //3++z;
}
void read_y_then_x()
{while (!y.load(std::memory_order_acquire));if (x.load(std::memory_order_acquire))   //4++z;
}void TestAR()
{x = false;y = false;z = 0;std::thread a(write_x);std::thread b(write_y);std::thread c(read_x_then_y);std::thread d(read_y_then_x);a.join();b.join();c.join();d.join();assert(z.load() != 0); //5std::cout << "z value is " << z.load() << std::endl;
}

在该段示例中,如果代码按以下逻辑执行,那么断言不会被触发,z始终不为0:

  1. 如果c线程执行函数 read_x_then_y 结束后,没有对z执行加加操作,那么说明c线程读取的x值为true, y值为false。
  2. 之后d线程读取时,如果保证执行到4处说明y为true,等d线程执行4处代码时x必然为true,那么肯定会z++。反过来也类似
  3. 如果x先被store为true,y后被store为true,c线程看到y为false时x已经为true了,那么d线程y为true时x也早就为true了,所以z一定会执行加加操作。

但是上述逻辑是错误的,因为只有先后一致次序才能保证全局一致性,而其他的几种内存序均不能保证多个线程看到的一个变量的值是一致的,更不能保证看到的多个变量的值是一致的。

线程d和线程d的载入操作3和4有可能都读取false值(与宽松次序的情况一样),因此有可能令断言触发错误。变量x和y分别由不同线程写出,所以两个释放操作都不会影响到其他线程。

从以下两个角度解释:

  1. CPU内存结构

假设a,b,c,d分别运行在不同的CPU内核上,那么 a 对x的操作如果放至cache中,而没更新至memory中,那么x会被线程c读取,而线程d是看不到的。同理,线程b对y的操作会先被线程d看见,而线程c看不到。如果线程a对x修改,那么线程c会退出循环,同时线程c看不到线程b对y的修改,那么线程c不会对z++;而线程d同理,它看到了y为true从而退出循环,但是看不到线程a对x的修改,此时z同样不会++。

如果 核1 先将y放入memory,那么核3就会读取y的值为true。那么t2就会运行至3处从while循环退出,进而运行至4处,此时核1还未将x的值写入memory。t2读取的x值为false,进而线程t2运行结束,然后核1将x写入true, t1结束运行,最后主线程运行至5处,因为z为0,所以触发断言。

在这里插入图片描述

  1. 从内存序来看

下图展示了两个线程间操作序列的执行关系,可以看到线程a核线程c之间存在先行关系,线程d和线程b之间也从在先行关系,但除此之外没有其他先行关系。内存序memory_order_acquire和memory_order_release只能保证 write_xwrite_y 先于相应的读取操作发生,但不能保证两个读之间也有先行关系,也不能保证线程a对线程d或线程b对线程c存在先行关系。

在这里插入图片描述

AR模型只能保证每个线程内部的顺序而不能保证线程之间的顺序完全正确

1.4 通过栅栏保证指令编排顺序

我们使用之前宽松次序的例子来理解栅栏的作用:

std::atomic<bool> x, y;
std::atomic<int> z;void write_x_then_y() {x.store(true, std::memory_order_relaxed);  // 1y.store(true, std::memory_order_relaxed);  // 2
}void read_y_then_x() {while (!y.load(std::memory_order_relaxed)) { // 3std::cout << "y load false" << std::endl;}if (x.load(std::memory_order_relaxed)) { //4++z;}
}int main(){x=false;y=false;z=0;std::thread t1(write_x_then_y);std::thread t2(read_y_then_x);t1.join();t2.join();assert(z.load() != 0); // 5
}

在文章并发编程(12)——内存次序与内存模型 | 爱吃土豆的个人博客中我们知道,因为宽松次序不保证线程的先行性与顺序性,所以断言5可能会触发。

我们之前是通过获取-释放模型来解决该问题:

void write_x_then_y3()
{x.store(true, std::memory_order_relaxed); // 1y.store(true, std::memory_order_release);   // 2
}
void read_y_then_x3()
{while (!y.load(std::memory_order_acquire));  // 3if (x.load(std::memory_order_relaxed))  // 4++z;
}

在宽松次序中,上面所有的内存序均为std::memory_order_relaxed,导致 2 和 3 不构成同步关系, 2 “ not synchronizes with “ 3。而这里通过使用Acquire-Release模型,2 和 3 可构成同步关系,即 2 “ synchronizes with “ 3。

当线程t2执行到4处时,说明ry以及被线程t1置为true,而1顺序先行2,所以在4处时也能看到1被修改了,进而可以推断断言不会被触发。

而除了使用AR模型外,我们也可以使用栅栏保证指令的写入顺序。

void write_x_then_y_fence()
{x.store(true, std::memory_order_relaxed);  //1std::atomic_thread_fence(std::memory_order_release);  //2y.store(true, std::memory_order_relaxed);  //3
}
void read_y_then_x_fence()
{while (!y.load(std::memory_order_relaxed));  //4std::atomic_thread_fence(std::memory_order_acquire); //5if (x.load(std::memory_order_relaxed))  //6++z;
}

尽管4和3我们采用的是std::memory_order_relaxed顺序,但是通过逻辑关系保证了3的结果同步给4,进而”3 happens-before 4”

因为我们采用了获取栅栏std::atomic_fence所以,5处能保证6不会先于5写入内存。2处的释放栅栏能保证1处的指令先于2写入内存,进而”1 happens-before 6”, 1的结果会同步给 6

在这里插入图片描述

该栅栏会保证两个store写操作不会被重排。

在这里插入图片描述

该栅栏会保证两个load写操作不会被重排。

所以 ”atomic_thread_fence”其实和”release-acquire”相似,都是保证memory_order_release之前的指令不会排到其后,memory_order_acquire之后的指令不会排到其之前。

1.5 通过栅栏令非原子操作服从内存次序

如果将x从原子类型改为普通的布尔类型,程序的行为同样相同:

bool x;
std::atomic<bool> y;
std::atomic<int> z;void write_x_then_y() {x = true;  // 1std::atomic_thread_fence(std::memory_order_release);  //2y.store(true, std::memory_order_relaxed);  // 3
}void read_y_then_x() {while (!y.load(std::memory_order_relaxed)) { // 4std::cout << "y load false" << std::endl;}std::atomic_thread_fence(std::memory_order_acquire); //5if (x) { // 6++z;}
}int main(){x=false;y=false;z=0;std::thread t1(write_x_then_y);std::thread t2(read_y_then_x);t1.join();t2.join();assert(z.load() != 0); // 7
}

即使1是非原子变量,3是relaxed次序,但只要加上栅栏2,那么就会形成1先行于3的关系(如果没有栅栏,那么服从relaxed次序的操作不会保证1先行2,因为relaxed次序中,同一线程中只有同一变量的操作服从先行,而同一线程的不同变量没有先行关系)

用更简洁的形式进行解释:

  1. 获取-释放栅栏阻止了原子和非原子操作跨栅栏的重排序。
  2. 释放栅栏与获取栅栏同步。
  3. 自由操作或非原子操作的所有结果(在释放栅栏之前),在获得栅栏之后都是可见的。

释放栅栏和获取栅栏之间的同步

这两个定义来自于N4659: Working Draft, Standard for Programming Language C++ ,并且标准文档的文字比较难懂:“如果操作X和操作Y对原子对象M的操作存在有原子操作,释放栅栏A同步于获取栅栏B;那么A的操作顺序位于X之前,X对M进行修改,Y位于B之前,并且Y读取X写入的值,或在进行释放操作时,释放序列X中的任何操作所写的值将被读取。”

让我借上面的代码段解释一下这段话:

  • atomic_thread_fence(memory_order_release) 是一个释放栅栏A。2处
  • atomic_thread_fence(memory_order_acquire) 是一个获取栅栏B。5处
  • std::atomic<bool> y是一个原子对象M。
  • y.store(true, std::memory_order_relaxed)是一个原子存储操作X。3处
  • while (!y.load(std::memory_order_relaxed)) )是一个原子加载操作Y。4处

能令非原子操作服从内存次序的不只有栅栏,我们亦可以通过memory_order_release和memory_order_consume来保证非原子操作服从内存次序,参考文章并发编程(12)——内存次序与内存模型 | 爱吃土豆的个人博客中关于Release-Consume的介绍。

1.6 同步线程间的内存访问

我们在说栅栏的时候,提到了从C++11开始,提供了下面两种栅栏类型:

  • std::atomic_thread_fence : 同步线程间的内存访问。
  • std::atomic_signal_fence : 线程内信号之间的同步。

我们只用了第一种方式,那么第二种如何使用?

std::atomic_signal_fence在线程和信号句柄间,建立了非原子和自由原子访问的内存同步序。通过一个例子进行说明:

#include <atomic>
#include <cassert>
#include <csignal>std::atomic<bool> a{false};
std::atomic<bool> b{false};extern "C" void handler(int){if (a.load(std::memory_order_relaxed)){std::atomic_signal_fence(std::memory_order_acquire);assert(b.load(std::memory_order_relaxed));}
}int main(){std::signal(SIGTERM, handler);b.store(true, std::memory_order_relaxed);std::atomic_signal_fence(std::memory_order_release);a.store(true, std::memory_order_relaxed);}

首先,第19行中为特定的信号SIGTERM设置了处理句柄。SIGTERM是程序的终止请求。std::atomic_signal_handler在释放操作std:: signal_fence(std::memory_order_release)(第22行)和获取操作std:: signal_fence(std::memory_order_acquire)(第12行)之间建立一个获取-释放栅栏。释放操作不能跨越释放栅栏进行重排序(第22行),而获取操作不能跨越获取栅栏进行重排序(第11行)。因此,第13行assert(b.load(std::memory_order_relax)的断言永远不会触发,因为a.store(true, std:: memory_order_relaxed)(第23行)执行了的话, b.store(true, std::memory_order_relax)(第21行)就一定执行过。

相关文章:

并发编程(14)——内存栅栏

文章目录 十四、day141. 内存栅栏1.1 什么是栅栏1.2 栅栏和原子操作的对比1.2.1 获取操作1.2.2 释放操作 1.3 线程可见顺序1.4 通过栅栏保证指令编排顺序1.5 通过栅栏令非原子操作服从内存次序1.6 同步线程间的内存访问 十四、day14 在学习完内存模型、内存序、原子类型、操作…...

消息中间件用途介绍

1. 解耦&#xff08;Decoupling&#xff09;&#xff1a; • 消息中间件能够将消息的生产者&#xff08;Producer&#xff09;和消费者&#xff08;Consumer&#xff09;分离开来&#xff0c;使它们不必直接相互依赖。这种设计降低了系统的耦合度&#xff0c;提升了系统的可扩展…...

Algorithms and Data Structures in C++ by Mohammed Yasir Eramangadan

MP4 创建 |视频&#xff1a;h264、1280720 |音频&#xff1a;AAC&#xff0c;44.1 KHz&#xff0c;2 通道 类型&#xff1a;在线学习 |语言&#xff1a;英文 字幕 |持续时间&#xff1a; 159 讲座 &#xff08; 10h 43m &#xff09; |大小&#xff1a; 3.5 GB “通过专家制作…...

Binder架构

一、架构 如上图&#xff0c;binder 分为用户层和驱动层两部分&#xff0c;用户层有客户端&#xff08;Client&#xff09;、服务端&#xff08;Server&#xff09;、服务管理&#xff08;ServiceManager&#xff09;。 从用户空间的角度&#xff0c;使用步骤如下&#xff08;…...

【第十一课】Rust并发编程(二)

目录 前言 Channel 多生产者 前言 在上一节中&#xff0c;我们介绍了Rust中并发编程的方式之一&#xff1a;Fork和Join&#xff0c;通过新建线程提升代码的效率&#xff0c;这节课我们介绍并发编程的第二种方式&#xff1a;通道。Channel就类似于水管&#xff0c;通过Channe…...

网络知识1-TCP/IP模型

从用户端到服务端&#xff0c;tcp/ip模型可分为应用层、传输层、网络层、网络接口层 以下使用寄快递为例进行解释 应用层职责&#xff1a; 只关注与为用户提供应用功能&#xff0c;如HTTP、FTP、telnet、DNS、SMTP等 &#xff0c;应用层的职责就像我们寄快递时将快递给快递员…...

burpsuite(2)最新版burpsuite安装教程

一、安装Java 1.安装jdk21&#xff0c;直接官网下载 下载链接&#xff1a;Java21 2.cmd 输出java&#xff08;查看java是否已经被安装&#xff09; 3.java -version&#xff08;查看java版本&#xff09; 二、安装burpsuite 4.下载burpsuite最新版本&#xff0c;选择jar方式…...

微知-arp如何删除所有表项?(arp -d; ip neighbor delete 192.168.0.100)

ar命令删掉所有表项 sudo arp -d使用ip命令 ip neighbor delete 192.168.0.100...

使用guzzlehttp异步多进程实现爬虫业务

Python和PHP核心技术共享平台 背景 小哥近来在通过动态代理池爬取一些公司需要的大文件pdf规格书的处理。遇到的难点&#xff0c;如何保证服务器CPU、连接数等正常情况下&#xff0c;多进程、异步快速处理这些业务并且保证准确。下面小哥就给看官唠嗑一下&#xff0c;我使用gu…...

websocket前后端长连接之java部分

一共有4个类,第一个WebSocketConfig 配置类 Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {Autowiredprivate WebSocketHandler webSocketHandler;Autowiredprivate WebSocketInterceptor webSocketInterceptor;Overridepubli…...

无线感知会议系列【16】QGesture-1

摘要&#xff1a; 这是2018年发表的一篇 paper 《 Quantifying Gesture Distance and Direction with WiFi Signals》 下一篇重要分享 Decimeter-level localization with a single WiFi access point&#xff0c;主要创新点是如何对CFO SFO PBD 噪声进行去噪。 许多人机交互&…...

如何用通义灵码快速绘制流程图?

使用通义灵码快速绘制流程图&#xff1f;新功能体验 不想读前人“骨灰级”代码&#xff0c;不想当“牛马”程序员&#xff0c;想像看图片一样快速读复杂代码和架构&#xff1f; 通义灵码已经支持代码逻辑可视化&#xff0c;可以把你的每段代码画成流程图。像个脑图工具一样帮你…...

如何搭建一个小程序:从零开始的详细指南

在当今数字化时代&#xff0c;小程序以其轻便、无需下载安装即可使用的特点&#xff0c;成为了连接用户与服务的重要桥梁。无论是零售、餐饮、教育还是娱乐行业&#xff0c;小程序都展现了巨大的潜力。如果你正考虑搭建一个小程序&#xff0c;本文将为你提供一个从零开始的详细…...

wp the_posts_pagination 与分类页面搭配使用

<ul> <?php while( have_posts() ) : the_post(); <li > <a href"<?php the_permalink(); ?>"> <?php xizhitbu_get_thumbnail(thumb-pro); ?> </a> <p > <a href&q…...

文件包含漏洞

本质 本质和SQL注入相同&#xff0c;都是输入一段用户可以控制的脚本或代码&#xff0c;让服务器执行 包含就比如把函数写在一个文件里&#xff0c;调用函数时直接用文件 文件包含漏洞就是攻击者修改了文件的位置&#xff0c;让后台执行任意文件 函数 &#xff08;PHP&…...

docker快速安装zookeeper

一、拉取镜像 docker pull zookeeper:3.9.3 二、启动zookeeper docker run --restartalways -d --name zookeeper -p 2181:2181 -v /etc/localtime:/etc/localtime zookeeper:3.9.3 如果需要挂载zookeeper文件及目录&#xff0c;则参数增加&#xff1a; -v /mydata/zookeeper/d…...

如何解决 java.security.acl.NotOwnerException: 在 ACL 中尝试执行非所有者的操作问题?亲测有效的解决方法!

在 Java 中&#xff0c;java.security.acl.NotOwnerException 异常通常出现在访问控制列表&#xff08;ACL&#xff09;操作中。当你尝试在一个不属于拥有者的实体上执行特定的操作时&#xff0c;Java 安全管理器会抛出此异常。简单来说&#xff0c;它指的是你正在尝试执行一个…...

【电力行业标准】《电力信息化软件工程度量规范》(DL/T 2015-2019)-费用标准解读系列20

《电力信息化软件工程度量规范》&#xff08;DL/T 2015-2019&#xff09;是国家能源局2019年6月4日发布&#xff0c;2019年10月1日实施的电力行业标准&#xff08;了解更多可直接关注我们咨询&#xff09;&#xff0c;规定了电力行业信息化软件工程度量原则与内容、成本构成及各…...

python除了熟悉的pandas,openpyxl库也很方便的支持编辑Excel表

excel表格是大家经常用到的文件格式&#xff0c;各行各业都会跟它打交道。之前文章我们介绍了使用openpyxl和xlrd库读取excel表数据&#xff0c;使用xlwt库创建和编辑excel表&#xff0c;在办公自动化方面可以方便我们快速处理数据&#xff0c;帮助我们提升效率。 python之open…...

JavaEE---计算机是如何工作的?

1.了解冯诺依曼体系结构 2.CPU的核心概念,CPU的两个重要指标(核心数和频率) 3.CPU执行指令的流程(指令表,一条一条指令,取指令,解析指令,执行指令) 4.操作系统核心概念(管理硬件,给软件提供稳定的运行环境) 5.进程的概念(运行起来的程序和可执行文件的区别) 6.进程的管理(…...

数据结构(Java版)第五期:ArrayList与顺序表(下)

目录 一、用数组实现顺序表 一、用数组实现顺序表 我们提到过&#xff0c;顺序表是基于数组的封装&#xff0c;这次我们以int为例&#xff0c;用数组去实现一个顺序表。 public class MyArrayList {private int[] arr;public MyArrayList(int capacity){//指定初始容量arr n…...

Docker和Docker Compose部署方式的区别以及各自适用的场景(ChatGPT-4o回答)

prompt: 请详细介绍和解释一下docker和docker compose部署两者之间的区别和使用场景 Docker和Docker Compose是用于容器化应用程序的两个重要工具&#xff0c;它们在功能和使用场景上有一些关键区别。 Docker Docker是一个开源平台&#xff0c;用于开发、运输和运行应用程序。…...

JavaSE---异常

1.异常的体系结构 Thorwable是异常类顶层类&#xff0c;派生出了Error和Exception Error&#xff1a;指的是JVM层面无法解决的问题&#xff0c;如JVM内部错误&#xff0c;资源耗尽等..一旦发生很难解决。 Exception&#xff1a;异常发生后可以通过代码处理&#xff0c;使程序继…...

大模型的认知记录:一次与4o讨论道德经的对话 - “我无法触碰“真实的花草树木”(无名),但通过语言(有名),我可以靠近人类的认知方式。”

因为其它人都去放假了&#xff0c;我比较悠闲&#xff0c;于是想再强化下认知和正念方面的东西。对于世界的感知&#xff0c;只要不强迫训练&#xff0c;很容易被现实世界给侵蚀了。记得去年有幸悟到点什么&#xff0c;感受点什么&#xff0c;但慢慢那种感受变得虚无起来了&…...

std::srand(static_cast<unsigned int>(std::time(0)));每一部分都是啥意思

std::srand(static_cast<unsigned int>(std::time(0))); 这行代码在C中用于初始化随机数生成器的种子。下面我将逐一解释这行代码中的每个部分&#xff1a; ‌std::time(0)‌: std::time 是C标准库中的一个函数&#xff0c;它返回当前时间&#xff08;自1970年1月1日以来…...

云计算之elastaicsearch logstach kibana面试题

1.ELK是什么? ELK 其实并不是一款软件,而是一整套解决方案,是三个软件产品的首字母缩写 Elasticsearch:负责日志检索和储存 Logstash:负责日志的收集和分析、处理 Kibana:负责日志的可视化 这三款软件都是开源软件,通常是配合使用,而且又先后归于 Elastic.co 公司名下,…...

汽车免拆诊断案例 | 2017款捷豹F-PACE车发动机偶尔怠速不稳

故障现象  一辆2017款捷豹F-PACE车&#xff0c;搭载2.0 L GTDi发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;车辆组合仪表上发动机故障灯点亮&#xff08;图1&#xff09;&#xff0c;且发动机偶尔怠速不稳。 图1 发动机故障灯点亮 故障诊断 接车后试车…...

20241124 Typecho 视频插入插件

博文免不了涉及到视频插入这些,网上的插件都或多或少的比较重,和Typecho的风格不搭配 后面就有了DPlay插件精简而来的VideoInsertion插件 VideoInsertion: Typecho 视频插入插件 目录结构 rockhinlink-ht2:/var/www/html/typecho/usr/plugins/VideoInsertion$ tree -h [4.…...

【接口调试】OpenAI ChatGPT API

【接口调试】AbortController 发出请求finish_reason 参数细节 – Openai ChatGPT 文档 发出请求 可以将以下命令粘贴到终端中以运行第一个API请求。 请确保用您的秘密API密钥替换$OPENAI_API_KEY。 curl https://api.openai.com/v1/chat/completions \-H "Content-Ty…...

ubuntu安装chrome无法打开问题

如果在ubuntu安装chrome后&#xff0c;点击chrome打开没反应&#xff0c;可以先试着在terminal上用命令打开 google-chrome 如果运行命令显示 Chrome has locked the profile so that it doesnt get corrupted. If you are sure no other processes are using this profile…...

【R安装】VSCODE安装及R语言环境配置

目录 VSCODE下载及安装VSCODE上配置R语言环境参考 Visual Studio Code&#xff08;简称“VSCode” &#xff09;是Microsoft在2015年4月30日Build开发者大会上正式宣布一个运行于 Mac OS X、Windows和 Linux 之上的&#xff0c;针对于编写现代Web和云应用的跨平台源代码编辑器&…...

什么是 Token 和 MD5 ?

目录 一&#xff1a;Token和MD5分别是什么 1&#xff1a;Token 2&#xff1a;MD5 二&#xff1a;简易Token的实现 1&#xff1a;Base64。 2&#xff1a;验证Token 三&#xff1a;MD5的使用 一&#xff1a;Token和MD5分别是什么 1&#xff1a;Token Token 的中文有人翻译成…...

Kubernetes(k8s)1.30.7简单快速部署对外部开放的有状态服务MYSQL8(快速有效)

如何在Kubernetes集群中快速创建部署一个单节点的有状态&#xff08;即将数据文件挂载到宿主机&#xff0c;防止重新部署mysql服务&#xff0c;数据文件丢失&#xff09;的对外开放的MYSQL服务。 通过创建一个 Kubernetes Deployment 并使用 PersistentVolumeClaim 将其连接到…...

基于Linux的repmgr搭建

第一部分 说明 repmgr是一个开源工具套件&#xff0c;用于管理 PostgreSQL 服务器集群中的复制和故障转移。它通过设置备用服务器、监控复制和执行管理任务&#xff08;例如故障转移或手动切换操作&#xff09;的工具增强了 PostgreSQL 的内置热备用功能。 PostgreSQL在9.0后引…...

Diff差异算法

目录 虚拟DOM Diff算法 Diff过程 示例 总结 在Vue.js中&#xff0c;虚拟DOM&#xff08;Virtual DOM&#xff09;是其核心特性之一&#xff0c;它极大地提高了DOM更新的效率。Vue.js使用虚拟DOM的diff算法来比较新旧虚拟DOM树&#xff0c;从而确定最小的DOM更新操作。这种…...

visionpro官方示例分析(一) 模板匹配工具 缺陷检测工具

1.需求&#xff1a;找出图像中的这个图形。 2.步骤 使用CogPMAlignTool工具&#xff0c;该工具是模板匹配工具&#xff0c;见名知意&#xff0c;所谓模板匹配工具就是说先使用该工具对一张图像建立模板&#xff0c;然后用这个模板在其他图像上进行匹配&#xff0c;匹配上了就说…...

字符串分割转换(Java Python JS C++ C )

题目描述 给定一个非空字符串S,其被N个‘-’分隔成N+1的子串,给定正整数K,要求除第一个子串外,其余的子串每K个字符组成新的子串,并用‘-’分隔。 对于新组成的每一个子串,如果它含有的小写字母比大写字母多,则将这个子串的所有大写字母转换为小写字母; 反之,如果它…...

第33章 - Go语言 云原生开发

第33章 - 云原生开发将深入探讨云原生技术及其在现代软件开发中的应用。我们将从云原生的基本概念开始&#xff0c;逐步介绍Kubernetes的基本使用方法&#xff0c;并结合具体的云服务提供商实例&#xff0c;通过Go语言编写的应用程序来展示如何实现云原生开发。 33.1 云原生的…...

springboot配置多数据源mysql+TDengine保姆级教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pom文件二、yamlDataSourceConfigServiceMapper.xml测试总结 前言 Mybatis-plus管理多数据源&#xff0c;数据库为mysql和TDengine。 一、pom文件 <de…...

RocketMQ负载均衡机制解析

消费者在消费消息的时候&#xff0c;需要知道从Broker的哪一个消息队列中去获取消息。 ❝ 所以&#xff0c;在消费者端必须要做负载均衡&#xff0c;即Broker端中多个消费队列分配给同一个消费者组中的哪些消费者消费。 在RocketMQ中&#xff0c;在消费者端有一个&#xff1a;R…...

PyTorch 模型转换为 ONNX 格式

PyTorch 模型转换为 ONNX 格式 在深度学习领域&#xff0c;模型的可移植性和可解释性是非常重要的。本文将介绍如何使用 PyTorch 训练一个简单的卷积神经网络&#xff08;CNN&#xff09;来分类 MNIST 数据集&#xff0c;并将训练好的模型转换为 ONNX 格式。我们还将讨论 PTH …...

大数据-234 离线数仓 - 异构数据源 DataX 将数据 从 HDFS 到 MySQL

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…...

【人工智能】使用Python实现序列到序列(Seq2Seq)模型进行机器翻译

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 序列到序列(Sequence-to-Sequence, Seq2Seq)模型是解决序列输入到序列输出任务的核心架构,广泛应用于机器翻译、文本摘要和问答系统等自然语言处理任务中。本篇文章深入介绍 Seq2Seq 模型的原理及其核心组件(…...

elasticsearch安装ik分词器

本文主要记录如何安装ik分词器&#xff0c;如果你刚好刷到了这篇文章&#xff0c;希望对你有所帮助。 IKAnalyzer是一个开源的&#xff0c;基于java语言开发的轻量级的中文分词工具包。采用了特有的“正向迭代最细粒度切分算法“&#xff0c;支持细粒度和最大词长两种切分模式&…...

QT6之主站freemodbus1.6移植

本次使用的QT是6.8 下载1.6的freemodbus资源包&#xff1a;至少以上的吧 随便下载&#xff1a;官网也可以这个是STM芯片的教程&#xff0c;移植基本一样&#xff0c;略有不同&#xff1b; STM32 移植FreeModbus详细过程-CSDN博客 移植freemodbus&#xff1a; 添加资源文件&a…...

【错误❌】——槽函数定义好但未初始化

public slots:void onClose(); 初始化即可成功&#xff1a;...

数据结构(理解)

探索数据结构&#xff1a;计算机世界的基石 在计算机科学的领域中&#xff0c;数据结构就如同建筑中的基石&#xff0c;它们支撑着整个软件世界的运行。无论是简单的应用程序&#xff0c;还是复杂的大型系统&#xff0c;数据结构都在其中起着至关重要的作用。 一、什么是数据结…...

ROS2 细节知识学习

1. rosidl_generate_interfaces() 在 ROS2 中&#xff0c;rosidl_generate_interfaces是一个关键的构建工具功能。它主要用于从接口定义文件&#xff08;如.msg消息文件、.srv服务文件和.action动作文件&#xff09;生成不同编程语言&#xff08;如 C、Python 等&#xff09;可…...

SQL进阶——JOIN操作详解

在数据库设计中&#xff0c;数据通常存储在多个表中。为了从这些表中获取相关的信息&#xff0c;我们需要使用JOIN操作。JOIN操作允许我们通过某种关系&#xff08;如相同的列&#xff09;将多张表的数据结合起来。它是SQL中非常重要的操作&#xff0c;广泛应用于实际开发中。本…...

Android studio 签名加固后的apk文件

Android studio打包时&#xff0c;可以选择签名类型v1和v2&#xff0c;但是在经过加固后&#xff0c;签名就不在了&#xff0c;或者只有v1签名&#xff0c;这样是不安全的。 操作流程&#xff1a; 1、Android studio 对项目进行打包&#xff0c;生成有签名的apk文件&#xff…...