0X JavaSE-- 并发编程(ThreadGroup、JMM、volatile、synchronized、线程池)

news2024/11/19 15:16:28

ThreadGroup

线程组可以对线程进行批量控制。

  • 每个 Thread 必然存在于一个 ThreadGroup 中,Thread 不能独立于 ThreadGroup 存在。
  • 执行 main()方法的线程名字是 main。
  • 如果在 new Thread 时没有显式指定,那么默认将父线程(当前执行 new Thread 的线程)线程组设置为自己的线程组。
hread testThread = new Thread(() -> {
    System.out.println("testThread当前线程组名字:" +
            Thread.currentThread().getThreadGroup().getName());
    System.out.println("testThread线程名字:" +
            Thread.currentThread().getName());
});

testThread.start();
System.out.println("执行main所在线程的线程组名字: " + Thread.currentThread().getThreadGroup().getName());
System.out.println("执行main方法线程名字:" + Thread.currentThread().getName());
执行main所在线程的线程组名字: main
testThread当前线程组名字:main
testThread线程名字:Thread-0
执行main方法线程名字:main
  • ThreadGroup 是一个标准的向下引用的树状结构,父 ThreadGroup 可以引用其子 ThreadGroup 和子线程,但子 ThreadGroup 和子线程不能直接引用父 ThreadGroup,也不允许平级引用。这样设计可以防止"上级"线程被"下级"线程引用而无法被 GC 回收。
  • 线程组可以起到统一控制线程的优先级和检查线程权限的作用。

线程优先级

  • 线程优先级可以被人为指定,但 JVM只是给操作系统传达这个建议值,线程最终在操作系统中的优先级还是由操作系统决定。
    • 不过通常情况下,高优先级的线程将会比低优先级的线程有更高的概率得到执行。

JMM Java内存模型

Java 内存模型(Java Memory Model,JMM)定义了 Java 程序中的变量、线程如何和主存以及工作内存进行交互的规则。它主要涉及到多线程环境下的共享变量可见性、指令重排等问题,是理解并发编程中的关键概念。

作用

Java 线程之间的通信由 JMM 控制。

Java内存模型和运行时内存区域

Java 内存模型(JMM)和 Java 运行时内存区域的区别可大着呢。

Java 内存模型(Java Memory Model,JMM)

  • 定义:Java 内存模型描述了多线程程序中变量(包括实例字段、静态字段和数组元素)如何从主内存写入和读取,以及如何在主内存和工作内存(线程本地内存)之间传递。
  • 主要内容:
    • 可见性(Visibility): 当一个线程修改了某个变量的值,其他线程是否能立即看到这个修改。、
    • 原子性(Atomicity): 一个操作是不可分割的,不会被其他线程的操作所中断。
    • 有序性(Ordering): 程序执行的顺序是否与代码中的顺序一致。
  • 几个关键:
    • 主内存和工作内存: 所有变量都存储在主内存中,每个线程还有自己的工作内存,线程对变量的所有操作(读取和赋值)都必须在工作内存中进行。
    • volatile 关键字: 保证变量的可见性和有序性,但不保证原子性。
    • synchronized 关键字: 保证进入临界区的线程对变量的可见性和原子性。
    • happens-before 原则: 如果一个操作 happens-before 另一个操作,那么第一个操作的结果对第二个操作是可见的,且第一个操作的执行顺序排在第二个操作之前。

Java Runtime Memory Areas Java 运行时内存区域
在这里插入图片描述

  • 定义:JVM 在运行时将内存分为若干个不同的区域,用来存储不同类型的数据。这些区域包括:
    • 程序计数器(Program Counter Register):每个线程都有一个独立的程序计数器,用于记录当前线程执行的字节码指令的地址。
    • Java 虚拟机栈(JVM Stack):每个线程都有一个独立的虚拟机栈,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
    • 本地方法栈(Native Method Stack):与 Java 虚拟机栈类似,不过它服务于虚拟机使用到的本地方法(如 JNI)。
    • (Heap):堆是所有线程共享的一块内存区域,几乎所有的对象实例都在这里分配。堆是垃圾收集器管理的主要区域,根据垃圾收集器的不同,可以细分为新生代(Eden、From Survivor、To Survivor)和老年代。
    • 方法区(Method Area):方法区是所有线程共享的一块内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。在 JDK 8 之后,方法区称为元空间(Metaspace)。
    • 运行时常量池(Runtime Constant Pool):运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
共享堆内存带来的问题

对每一个线程来说,栈都是私有的,而堆是共有的。

也就是说,在栈中的变量(局部变量、方法定义的参数、异常处理的参数)不会在线程之间共享,也就不会有内存可见性的问题,也不受内存模型的影响。而在堆中的变量是共享的,一般称之为共享变量。

所以,内存可见性针对的是堆中的共享变量。

在这里插入图片描述
根据 JMM 的规定,线程对共享变量的所有操作都必须在自己的本地内存中进行,不能直接从主存中读取。
因此,各线程在本地内存中修改了共享变量,必须再写回到主内存,其它线程才能看到,如果还没写回到主内存,其它线程就访问了这个共享变量,就会造成可见性问题。

volatile关键字就是解决这个问题的。
对于可能被多线程并发修改访问的共享变量,加上 volatile关键字后,能确保:在本地内存修改的共享变量,立刻在主内存中生效。
用术语来说就是:保证多线程操作共享变量的可见性以及禁止指令重排序

在更底层,JMM 通过内存屏障来实现内存的可见性以及禁止重排序。为了程序员更方便地理解,设计者提出了 happens-before 的概念(下文会细讲),它更加简单易懂,从而避免了程序员为了理解内存可见性而去学习复杂的重排序规则,以及这些规则的具体实现方法。

指令重排序

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。
因为指令重排序可以提高性能。

处理器中普遍采用流水线技术,
但是流水线技术最害怕中断,恢复中断的代价是比较大的,所以我们要想尽办法不让流水线中断。

指令重排就是减少中断的一种技术。

  • 指令重排一般分为以下三种:
    • 编译器优化重排。编译器在不改变单线程程序语义的前提下,重新安排语句的执行顺序。
    • 指令并行重排。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序。
    • 内存系统重排。由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。

指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。所以在多线程下,指令重排序可能会导致一些问题。

程序可分为正确同步(使用 Volatile、Synchronized 等关键)、未正确同步的。

  • 对于已经正确同步的程序

    • 会改变程序执行结果的重排序,JMM 要求编译器和处理器都禁止这种重排序。
    • 不会改变程序执行结果的重排序,JMM 对编译器和处理器不做要求,允许这种重排序。
  • 对于未正确同步的程序

    • JMM 只提供最小安全性:线程读取到的值,要么是之前某个线程写入的值,要么是默认值,不会无中生有。(被后发线程先改变)

并发编程的两个问题

  • 并发编程的线程之间存在两个问题:
    • 线程间如何通信?即:线程之间以何种机制来交换信息
    • 线程间如何同步?即:线程以何种机制来控制不同线程间发生的相对顺序
  • 有两种并发模型可以解决这两个问题:
    • 消息传递并发模型
    • 共享内存并发模型

这两种模型之间的区别如下图所示:
在这里插入图片描述

Java 内存模型(JMM)定义了 Java 程序中的变量、线程如何和主存以及工作内存进行交互的规则。它主要涉及到多线程环境下的共享变量可见性、指令重排等问题,是理解并发编程中的关键概念。
Java 内存模型(JMM)主要针对的是多线程环境下,如何在主内存与工作内存之间安全地执行操作。
Java 运行时内存区域描述的是在 JVM 运行时,如何将内存划分为不同的区域,并且每个区域的功能和工作机制。主要包括方法区、堆、栈、本地方法栈、程序计数器。
指令重排是为了提高 CPU 性能,但是可能会导致一些问题,比如多线程环境下的内存可见性问题。
happens-before 规则是 JMM 提供的强大的内存可见性保证,只要遵循 happens-before 规则,那么我们写的程序就能保证在 JMM 中具有强的内存可见性。

volatile

  • volatile 作用
    • 保证可见性:当写一个 volatile 变量时,JMM 会把该线程在本地内存中的变量强制刷新到主内存中去;
    • 禁止指令重排
    • 但不保证原子性:
  • volatile 的实际应用
    • 在单例模式中,双重检查锁定(Double-Checked Locking)使用 volatile 确保线程安全地创建单例实例。
    • volatile 适用于需要在多个线程间共享并及时更新的状态变量,例如计数器、标志变量。

volatile 会禁止指令重排

重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。
使用 volatile 关键字修饰共享变量可以禁止这种重排序。

当使用 volatile 关键字来修饰一个变量时,Java 内存模型会插入内存屏障(一个处理器指令,可以对 CPU 或编译器重排序做出约束)来确保以下两点:

  • 写屏障(Write Barrier):当一个 volatile变量被写入时,写屏障确保在该屏障之前的所有变量的写入操作都提交到主内存。
  • 读屏障(Read Barrier):当读取一个 volatile变量时,读屏障确保在该屏障之后的所有读操作都从主内存中读取。

“也就是说,编译器和处理器执行到 volatile 变量时,必须将其前面的所有语句都必须执行完,后面所有得语句都未执行。且前面语句的结果对 volatile 变量及其后面语句可见。”

public class VolatileExample {
    private volatile boolean running = true;

    public void start() {
        Thread worker = new Thread(() -> {
            while (running) {
                // 执行一些工作
            }
            System.out.println("Thread stopped.");
        });
        worker.start();
    }

    public void stop() {
        running = false;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileExample example = new VolatileExample();
        example.start();
        
        Thread.sleep(1000); // 主线程等待一段时间
        example.stop();     // 停止工作线程
    }
}

volatile 不适用的场景

下面是变量自加的示例:

public class volatileTest {
    public volatile int inc = 0;
    public void increase() {
        inc++;
    }
    public static void main(String[] args) {
        final volatileTest test = new volatileTest();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println("inc output:" + test.inc);
    }
}

测试输出:

inc output:8182
“为什么呀?二哥?” 看到这个结果,三妹疑惑地问。

“因为 inc++不是一个原子性操作(前面讲过),由读取、加、赋值 3 步组成,所以结果并不能达到 10000。”我耐心地回答。

“哦,你这样说我就理解了。”三妹点点头。

怎么解决呢?

01、采用 synchronized(下一篇会讲,戳链接直达),把 inc++ 拎出来单独加 synchronized 关键字:

public class volatileTest1 {
public int inc = 0;
public synchronized void increase() {
inc++;
}
public static void main(String[] args) {
final volatileTest1 test = new volatileTest1();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(“add synchronized, inc output:” + test.inc);
}
}
02、采用 Lock,通过重入锁 ReentrantLock 对 inc++ 加锁(后面都会细讲,戳链接直达):

public class volatileTest2 {
public int inc = 0;
Lock lock = new ReentrantLock();
public void increase() {
lock.lock();
inc++;
lock.unlock();
}
public static void main(String[] args) {
final volatileTest2 test = new volatileTest2();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(“add lock, inc output:” + test.inc);
}
}
03、采用原子类 AtomicInteger(后面也会细讲,戳链接直达)来实现:

public class volatileTest3 {
public AtomicInteger inc = new AtomicInteger();
public void increase() {
inc.getAndIncrement();
}
public static void main(String[] args) {
final volatileTest3 test = new volatileTest3();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<100;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(“add AtomicInteger, inc output:” + test.inc);
}
}
三者输出都是 1000,如下:

add synchronized, inc output:1000
add lock, inc output:1000
add AtomicInteger, inc output:1000
#volatile 实现单例模式的双重锁
下面是一个使用"双重检查锁定"(double-checked locking)实现的单例模式(Singleton Pattern)的例子。

public class Penguin {
private static volatile Penguin m_penguin = null;

// 一个成员变量 money
private int money = 10000;

// 避免通过 new 初始化对象,构造方法应为 public 或 private
private Penguin() {}

public void beating() {
    System.out.println("打豆豆" + money);
}

public static Penguin getInstance() {
    if (m_penguin == null) {
        synchronized (Penguin.class) {
            if (m_penguin == null) {
                m_penguin = new Penguin();
            }
        }
    }
    return m_penguin;
}

}
在这个例子中,Penguin 类只能被实例化一次。来看代码解释:

声明了一个类型为 Penguin 的 volatile 变量 m_penguin,它是类的静态变量,用来存储 Penguin 类的唯一实例。
Penguin() 构造方法被声明为 private,这样就阻止了外部代码使用 new 来创建 Penguin 实例,保证了只能通过 getInstance() 方法获取实例。
getInstance() 方法是获取 Penguin 类唯一实例的公共静态方法。
第一次 if (null == m_penguin) 检查是否已经存在 Penguin 实例。如果不存在,才进入同步代码块。
synchronized(penguin.class) 对类的 Class 对象加锁,确保在多线程环境下,同时只能有一个线程进入同步代码块。在同步代码块中,再次执行 if (null == m_penguin) 检查实例是否已经存在,如果不存在,则创建新的实例。这就是所谓的“双重检查锁定”,一共两次。
最后返回 m_penguin,也就是 Penguin 的唯一实例。
其中,使用 volatile 关键字是为了防止 m_penguin = new Penguin() 这一步被指令重排序。因为实际上,new Penguin() 这一行代码分为三个子步骤:

步骤 1:为 Penguin 对象分配足够的内存空间,伪代码 memory = allocate()。
步骤 2:调用 Penguin 的构造方法,初始化对象的成员变量,伪代码 ctorInstanc(memory)。
步骤 3:将内存地址赋值给 m_penguin 变量,使其指向新创建的对象,伪代码 instance = memory。
如果不使用 volatile 关键字,JVM 可能会对这三个子步骤进行指令重排。

为 Penguin 对象分配内存
将对象赋值给引用 m_penguin
调用构造方法初始化成员变量
这种重排序会导致 m_penguin 引用在对象完全初始化之前就被其他线程访问到。具体来说,如果一个线程执行到步骤 2 并设置了 m_penguin 的引用,但尚未完成对象的初始化,这时另一个线程可能会看到一个“半初始化”的 Penguin 对象。

假如此时有两个线程 A 和 B,要执行 getInstance() 方法:

public static Penguin getInstance() {
if (m_penguin == null) {
synchronized (Penguin.class) {
if (m_penguin == null) {
m_penguin = new Penguin();
}
}
}
return m_penguin;
}
线程 A 执行到 if (m_penguin == null),判断为 true,进入同步块。
线程 B 执行到 if (m_penguin == null),判断为 true,进入同步块。
如果线程 A 执行 m_penguin = new Penguin() 时发生指令重排序:

线程 A 分配内存并设置引用,但尚未调用构造方法完成初始化。
线程 B 此时判断 m_penguin != null,直接返回这个“半初始化”的对象。
这样就会导致线程 B 拿到一个不完整的 Penguin 对象,可能会出现空指针异常或者其他问题。

于是,我们可以为 m_penguin 变量添加 volatile 关键字,来禁止指令重排序,确保对象的初始化完成后再将其赋值给 m_penguin。

#小结
“好了,三妹,我们来总结一下。”我舒了一口气说。

volatile 可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在 JVM 底层 volatile 是采用“内存屏障”来实现的。

观察加入 volatile 关键字和没有加入 volatile 关键字时所生成的汇编代码就能发现,加入 volatile 关键字时,会多出一个 lock 前缀指令,lock 前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供 3 个功能:

它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
它会强制将对缓存的修改操作立即写入主存;
如果是写操作,它会导致其他 CPU 中对应的缓存行无效。
最后,我们学习了 volatile 不适用的场景,以及解决的方法,并解释了双重检查锁定实现的单例模式为何需要使用 volatile。

synchronized

synchronized 可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到 synchronized 的另外一个重要的作用,synchronized 可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代 volatile 功能)。

synchronized 关键字最主要有以下 3 种应用方式:

同步方法,为当前对象(this)加锁,进入同步代码前要获得当前对象的锁;
同步静态方法,为当前类加锁(锁的是 Class 对象),进入同步代码前要获得当前类的锁;
同步代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized 同步方法

synchronized 同步方法

synchronized 同步方法

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

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

相关文章

Win11 恢复快捷方式箭头

Win11 恢复快捷方式箭头 前言步骤 前言 本作者习惯了当文件类型是快捷方式时左下角有个小箭头。但无语的是&#xff0c;我重装了 Win 11 系统后&#xff0c;快捷方式中没有了小箭头&#xff0c;当真抓狂。啊&#xff01;&#xff01;&#xff01;查了那么多资料&#xff0c;很多…

Spring Cloud Gateway 与 Nacos 的完美结合

在现代微服务架构中&#xff0c;服务网关扮演着至关重要的角色。它不仅负责路由请求到相应的服务&#xff0c;还承担着诸如负载均衡、安全认证、限流熔断等重要功能。Spring Cloud Gateway 作为 Spring Cloud 生态系统中的一员&#xff0c;以其强大的功能和灵活的配置&#xff…

浏览器扩展V3开发系列之 chrome.commands 快捷键的用法和案例

【作者主页】&#xff1a;小鱼神1024 【擅长领域】&#xff1a;JS逆向、小程序逆向、AST还原、验证码突防、Python开发、浏览器插件开发、React前端开发、NestJS后端开发等等 chrome.commands API 允许开发者使用快捷键来执行特定的命令。 在使用 chrome.commands API 之前必须…

明星周边物品交易购物系统

摘 要 随着明星文化的兴起和粉丝经济的蓬勃发展&#xff0c;明星周边产品的市场需求日益增长。明星周边物品包括各种与明星相关的商品&#xff0c;如T恤、海报、手办、签名照等&#xff0c;它们成为粉丝们表达对明星喜爱和支持的方式之一。通过“星光璀璨”来形象化地表达明星…

基于PHP的酒店管理系统(改进版)

有需要请加文章底部Q哦 可远程调试 基于PHP的酒店管理系统(改进版) 一 介绍 此酒店管理系统(改进版)基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端jquery插件美化。系统角色分为用户和管理员。系统在原有基础上增加了注册登录注销功能&#xff0c;增加预订房间图片…

植物大战僵尸杂交版v2.1最新整合版,附PC端+安卓端+iOS端安装包+修改器+安装教程!

嘿&#xff0c;大家好&#xff0c;我是阿星&#xff0c;今天要跟大家聊聊一款游戏&#xff0c;它不是那种让人眼花缭乱的大制作&#xff0c;也不是那种能让人回味无穷的艺术作品&#xff0c;但它在阿星心里&#xff0c;绝对是神作中的佼佼者。没错&#xff0c;它就是《植物大战…

6.22套题

B. Dark 题意&#xff1a;每次能在数列中能使相邻两个数-1&#xff0c;求当数列没有连续非0值的最小贡献 解法:设表示前i个数中前i-1个数是否为0&#xff0c;当前数是j的最小贡献。表示i1以后减掉d的最小贡献。 C. 幸运值 D. 凤凰院真凶

【perl】脚本编程的一些坑案例

引言 记录自己跳进的【perl】编程小坑&#xff0c;以己为鉴。 1、eq $str1 "12345\n"; $str2 "12345"; if ($str1 eq $str2) { print "OK" } 上述代码不会打印 OK。特别在读文件 &#xff0c;匹配字符串时容易出BUG。 案例说明&#xff1a; 有…

PID原理及控制算法详解

文章目录 1. 概念 1.1 PID框图 1.2 具体示例&#xff1a;无人机高度控制 2. PID原理 3. 常用术语 4. 计算过程 4.1 比例控制&#xff08;Proportional&#xff09; 4.2 积分控制&#xff08;Integral&#xff09; 4.3 微分控制&#xff08;Derivative&#xff09; 5.…

NLP逻辑层次模型|跳出局限,站在更高维度认识自己

什么是NLP逻辑层次模型 N-Neuro&#xff1a;指神经系统&#xff0c;包括生理基础&#xff08;大脑&#xff09;和思维运作过程 L-Linguistic&#xff1a;指语言&#xff0c;感觉信号输出——构成意思的过程 P-Programming&#xff1a;指程序&#xff0c;大脑产生某结论后要具体…

Java基础简要(基础、集合、正则、时间类、异常、Stream流、File类、IO流、多线程、数据结构、泛型)

一位等于1比特。比特&#xff08;BIT&#xff09;和位是同一个概念的不同表述&#xff0c;都是信息量的最小单位。1字节&#xff08;byte&#xff09;由8位组成。 1.介绍 JVM&#xff08;Java Virtual Machine&#xff09;&#xff1a;Java虚拟机, 真正运行Java程序的地方核…

Python | 使用Matplotlib生成子图的示例

数据可视化在分析和解释数据的过程中起着举足轻重的作用。Python中的Matplotlib库提供了一个强大的工具包&#xff0c;用于制作各种图表和图表。一个突出的功能是它能够在单个图中生成子图&#xff0c;为以组织良好和结构化的方式呈现数据提供了有价值的工具。使用子图可以同时…

华为---VRRP基本配置(一)

10、VRRP 10.1 VRRP基本配置 10.1.1 原理概述 随着Internet的发展&#xff0c;人们对网络可靠性的要求越来越高。对于用户来说&#xff0c;能够时刻与外部网络保持通信非常重要&#xff0c;但内部网络中的所有主机通常只能设置一个网关IP地址&#xff0c;通过该出口网关实现…

Flutter TIM 项目实现

目录 1. 服务端API 1.1 生成签名 1.1.1 步骤 第一步:获取签名算法 第二步:查看函数输入输出 第三步:nodejs 实现功能 1.1.2 验证签名 小结 1.2 Rest API 调用 1.2.1 签名介绍 1.2.2 腾讯接口 生成管理员 administrator 签名 包装一个 post 请求函数 查询账号 …

发;flask的基本使用2

上一篇我们介绍了基本使用方法 flask使用 【 1 】基本使用 from flask import Flask# 1 实例化得到对象 app Flask(__name__)# 2 注册路由--》写视图函数 app.route(/) def index():# 3 返回给前端字符串return hello worldif __name__ __main__:# 运行app&#xff0c;默认…

c++11 abi 兼容性

理解 _GLIBCXX_USE_CXX11_ABI: 兼容性与现代化之间的平衡 随着 C 标准的不断演进&#xff0c;编译器和标准库实现也在不断更新&#xff0c;以支持新的语言特性和库功能。然而&#xff0c;这些更新有时会引入不兼容的更改&#xff0c;特别是应用程序二进制接口&#xff08;ABI&…

第N8周:seq2seq翻译实战-Pytorch复现

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、前期准备 from __future__ import unicode_literals, print_function, division from io import open import unicodedata import s…

Jenkins通过Squid代理服务器添加局域网节点机器

✨前言&#xff1a; 当jenkins在公网上的时候&#xff0c;如果要添加局域网内的服务器为节点机器构建的时候&#xff0c;这里就需要通过squid代理服务来实现了。当然你也可以使用其他的方式例如Apache等等&#xff0c;这里主要介绍通过Squid的方式。 &#x1f31f;什么是Squi…

通过颜色传感器控制机械臂抓物体

目录 1 绪论 2整体设计方案 2.1 系统的介绍 2.2 抓取模块 2.2.1 机械臂的定义 2.2.2 机械臂的分类 2.2.3 机械臂的选用 2.3 颜色识别模块 2.3.1 颜色传感器识别原理 2.3.2 TCS3200简介 2.4 整体控制方案 3 颜色识别抓取系统的硬件设计 3.1 单片机选型及参数 3.2 系…

第三十二篇——大数据2:大数据思维的四个层次

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 我们生活在这个时代&#xff0c;我们是否按照这个时代需要的思维方式去思…