【iOS】—— 初识GCD

news2024/11/20 9:42:13

GCD(Grand Central Dispatch)

文章目录

  • GCD(Grand Central Dispatch)
    • 什么是GCD
      • performSelector方法:
      • GCD的优点:
    • 任务和队列
      • 队列的创建方式
      • 任务的创建方法
      • 六种情况的例子
        • 1.并发队列 + 同步执行
        • 2. 并发队列 + 异步执行
        • 3.串行队列 + 同步执行
        • 4.串行队列 + 异步执行
        • 5. 主队列 + 同步执行
        • 6. 主队列 + 异步执行
    • GCD线程之间的通讯
    • GCD的栅栏方法
    • GCD的延时执行方法
      • GCD的一次性代码(只执行一次)
      • GCD的队列组
    • 未完待续······

什么是GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
例子

//让处理在后台线程中执行
dispatch async(queue, ^{
	/*
	 *长时间处理
	 *例如AR用画像识别*例如数据库访问
	 */
	/*
	 *长时间处理结束, 主线程使用该处理结果。 
	 */

    //让处理在主线程中执行
	dispatch_async(dispatch_get main_queue(), ^{
		/*
		 *只在主线程可以执行的处理
		 *例如用户界面更新
		 */
	});
});

其实在GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackground:withObject 实例方法和 performSelectorOnMainThread 实例方法等简单的多线程编程技术。

performSelector系方法要比使用NSThread 类进行多线程编程简单,但与之前使用GCD的源代码相比,结果一目了然。相比performSelector系方法,GCD更为简洁。如果使用GCD,不仅不必使用NSThread类或performSelector系方法这些过时的API,更可以通过GCD提供的系统级线程管理提高执行效率。

performSelector方法:

在这里插入图片描述

GCD的优点:

  • GCD可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

任务和队列

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

  • 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力

队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并发队列。

  • 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)【并发功能只有在异步(dispatch_async)函数下才有效】
  • 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

在这里插入图片描述

队列的创建方式

可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

对于并发队列,还可以使用dispatch_get_global_queue来创建全局并发队列。GCD默认提供了全局的并发队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});

我们知道有两种任务,加上主队列有三种队列,那么就有了6种组合方式,我们来看看6种组合方式的不同效果:

  • 并发队列 + 同步执行
  • 并发队列 + 异步执行
  • 串行队列 + 同步执行
  • 串行队列 + 异步执行
  • 主队列 + 同步执行
  • 主队列 + 异步执行

在这里插入图片描述

六种情况的例子

1.并发队列 + 同步执行

- (void)syncConcurrent {          // 并发队列 + 同步执行
    NSLog(@"syncConcurrent---begin");
 
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
 
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
 
    NSLog(@"syncConcurrent---end");
}

运行结果:
在这里插入图片描述

从并发队列 + 同步执行中可以看到,所有任务都是在主线程中执行的。由于只有一个线程,所以任务只能一个一个执行。
同时我们还可以看到,所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的。

2. 并发队列 + 异步执行

- (void)asyncConcurrent {            //并发队列 + 异步执行
    NSLog(@"asyncConcurrent---begin");
 
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
 
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncConcurrent---end");
}

运行结果:
在这里插入图片描述

在并发队列 + 异步执行中可以看出,除了主线程,又开启了3个线程,并且任务是交替着同时执行的。
另一方面可以看出,所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行。

3.串行队列 + 同步执行

- (void)syncSerial {             //串行队列 + 同步执行
    NSLog(@"syncSerial---begin");
 
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
 
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"syncSerial---end");
}

在这里插入图片描述

在串行队列 + 同步执行可以看到,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行。
同时我们还可以看到,所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的。

4.串行队列 + 异步执行

- (void)asyncSerial {         //串行队列 + 异步执行
    NSLog(@"asyncSerial---begin");
 
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
 
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncSerial---end");
}

运行结果:
在这里插入图片描述

在串行队列 + 异步执行可以看到,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行。
另一方面可以看出,所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

5. 主队列 + 同步执行

- (void)syncMain {     //主队列 + 同步执行
    NSLog(@"syncMain---begin");
 
    dispatch_queue_t queue = dispatch_get_main_queue();
 
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
 
    NSLog(@"syncMain---end");
}

这段代码运行会崩溃,编译器给出如下报错:
在这里插入图片描述

仔细检查可以发现,我们这段代码就是在主线程中运行,而在主线程中将任务放在了主队列中,同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行。但是主线程现在正在处理syncMain方法,所以任务需要等syncMain执行完才能执行。而syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。
那么,现在的情况就是syncMain方法和第一个任务都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain—end也没有打印。

要是如果不再主线程中调用,而在其他线程中调用会如何呢?
先将任务放在其他线程,然后再回到主线程:

//将方法调用改为
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
 
dispatch_async(queue, ^{
    [self syncMain];
});

在这里插入图片描述

在其他线程中使用主队列 + 同步执行可看到:所有任务都是在主线程中执行的,并没有开启新的线程。而且由于主队列是串行队列,所以按顺序一个一个执行。
同时我们还可以看到,所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的。

6. 主队列 + 异步执行

- (void)asyncMain {          //主队列 + 异步执行
    NSLog(@"asyncMain---begin");
 
    dispatch_queue_t queue = dispatch_get_main_queue();
 
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@", [NSThread currentThread]);
        }
    });
 
    NSLog(@"asyncMain---end");
}

在这里插入图片描述

我们发现所有任务都在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并且一个接一个执行。
另一方面可以看出,所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

GCD线程之间的通讯

在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 6; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
     
        // 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@",[NSThread currentThread]);
        });
    });

输出结果:
在这里插入图片描述

可以看到在其他线程中先执行操作,执行完了之后回到主线程执行主线程的相应操作。

GCD的栅栏方法

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

- (void)barrier {      //GCD栅栏方法
    dispatch_queue_t queue = dispatch_queue_create("666", DISPATCH_QUEUE_CONCURRENT);
 
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
 
//    dispatch_barrier_async(queue, ^{
//        NSLog(@"----barrier-----%@", [NSThread currentThread]);
//    });
 
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

首先我们把栅栏方法注释起来,运行试试:
在这里插入图片描述
在这里插入图片描述
我们会发现四个数字几乎同时打印,每次顺序是随机的,这时候把栅栏方法加上。

    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

在这里插入图片描述

GCD的延时执行方法

当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

- (void)after {
    NSLog(@"run -- 0");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2秒后异步执行这里的代码...
       NSLog(@"run -- 2");
    });
}

在这里插入图片描述

GCD的一次性代码(只执行一次)

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。

    [self once];
    [self once];

- (void)once {        //GCD的一次性代码(只执行一次)
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
        NSLog(@"666");
    });

}

运行结果:
在这里插入图片描述

GCD的队列组

有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。

  • 我们可以先把任务放到队列中,然后将队列放入队列组中。
  • 调用队列组的dispatch_group_notify回到主线程执行操作。
- (void)group {        //GCD的队列组
    dispatch_group_t group =  dispatch_group_create();
     
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        int i = 0;
        while (i < 100) {
            NSLog(@"1");
            i++;
        }
        
    });
     
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        int i = 0;
        while (i < 100) {
            NSLog(@"2");
            i++;
        }
    });
     
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
        NSLog(@"3");
    });

}

运行结果:
在这里插入图片描述

参考大佬博客:iOS多线程–彻底学会多线程之『GCD』

关于GCD的内容还有很多,在以后学习中会继续补充。

未完待续······

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

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

相关文章

Vector - VT System - Ethernet板卡_VT6306

前面介绍了支持CAN&CANFD&LIN板卡&#xff0c;但是对于当前日益火爆的车载以太网来说&#xff0c;Vector也是提供了类似于VN5000系列一样的板卡&#xff0c;那就是VT6306。它给提供6路的百兆或者6路千兆的车载以太网&#xff08;2022年之前选择后是固定的&#xff0c;有…

央视点赞百度智能云激活民营经济”数字“活力

2023年&#xff0c;对民营企业究竟意味着什么。 2022年12月&#xff0c;新华社发表重磅长文解读中国经济发展大势&#xff1a;“迎接更加壮阔的光明前程”。 随后央视新闻联播连续4天发声&#xff0c;关注民营经济发展。4条新闻中&#xff0c;“创新”一词共出现了29次&#…

小程序的运行机制以及安全机制

接触小程序有一段时间了&#xff0c;总得来说小程序开发门槛比较低&#xff0c;但其中基本的运行机制和原理还是要懂的. 了解小程序的由来 在小程序没有出来之前&#xff0c;最初微信WebView逐渐成为移动web重要入口&#xff0c;微信发布了一整套网页开发工具包&#xff0c;称…

199:vue+openlayers 添加删除修改feature信息,双向不同颜色指示互动

第199个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中绘制多边形,每绘制一个,左侧输出一个feature指示标志,双向颜色互动指示。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意如果OpenStreetMap无法加载,请加载其他来练习 …

前缀和讲解

目录 一、前言 二、前缀和 1、基本概念 2、前缀和与差分的关系 3、差分数组能提升修改的效率 三、例题 1、统计子矩阵&#xff08;lanqiao2109&#xff0c;2022年省赛&#xff09; &#xff08;1&#xff09;处理输入 &#xff08;2&#xff09;方法一&#xff1a;纯暴…

设计模式面试题

工厂模式是我们最常用的实例化对象模式了&#xff0c;是用工厂方法代替new操作的一种模式,工厂模式在Java程序中可以说是随处可见。本文来给大家详细介绍下工厂模式 面向对象设计的基本原则&#xff1a; OCP&#xff08;开闭原则&#xff0c;Open-Closed Principle&#xff0…

字符串函数介绍——C语言

文章目录 一、引言 二、函数的介绍与模拟实现 2、1 求字符串长度strlen&#xff08;&#xff09;函数 2、1、1 strlen&#xff08;&#xff09;函数介绍 2、1、2 strlen&#xff08;&#xff09;函数的模拟实现 2、2 字符串拷贝strcpy&#xff08;&#xff09;函数 2、2、1 s…

「旷野俱乐部」在 The Sandbox 开业,SMCU 宫殿等你来体验!

简要概括 KWANGYAThe Sandbox 是「旷野俱乐部」在 The Sandbox 元宇宙中的虚拟空间&#xff1b; SMCU 宫殿体验呈现了 2022 年冬季 SM 小镇的视觉效果&#xff0c;SMCU 宫殿专辑封面将于 1 月 10 日發佈&#xff1b; 将向全球粉丝展示更多基于韩国文化内容的元宇宙体验。 The…

Appium+Pytest+pytest-testreport框架轻松实现app自动化

有任何环境问题&#xff0c;可以参考我的文章 Appium自动化测试&#xff1c;一&#xff1e;&#xff0c; Appium自动化测试&#xff1c;二&#xff1e;有任何定位问题、触屏操作、等待问题、Toast 信息操作问题、手机操作问题及H5页面的操作请参考我的文章&#xff1a;Appium自…

【论文速递】TNNLS2022 - 一种用于小样本分割的互监督图注意网络_充分利用有限样本的视角

【论文速递】TNNLS2022 - 一种用于小样本分割的互监督图注意网络_充分利用有限样本的视角 【论文原文】&#xff1a;A Mutually Supervised Graph Attention Network for Few-Shot Segmentation: The Perspective of Fully Utilizing Limited Samples 获取地址&#xff1a;ht…

Java设计模式-组合模式Composite

介绍 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;它创建了对象组的树形结构&#xff0c;将对象组合成树状结构以表示“整体-部分”的层次关系。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。这种类…

【Nginx】Nginx的常用命令和配置文件

1. 常用命令 1. 查看版本2. 查看 Nginx 配置语法的正确性3. 为Nginx指定一个配置文件4. 启动 Nginx 服务5. 开机自启动6. 重启 Nginx 服务7. 查看 Nginx 服务状态8. 重载 Nginx 服务9. 停止 Nginx 服务10. 查看命令帮助 2. 配置文件 第一部分&#xff1a;全局块第二部分&#x…

RT-Thread系列--内存池MEMPOOL源码分析

一、目的嵌入式RTOS中最重要也是最容易被忽略的一个组件就是内存管理&#xff0c;像FreeRTOS单单内存管理组件就提供了heap_1/2/3/4/5这五种方案&#xff0c;每种方案都有其特点和应用场景。一般情况下小系统所运行的芯片平台本身内存就很少&#xff0c;有些时候内存空间还不连…

libdrm-2.4.112

编译 这个版本使用了meson进行构建、ninja进行编译 &#xff1b; 安装meson 编译 报错如上&#xff0c;查看meson.build文件&#xff0c; 我们的meson版本不正确&#xff0c; 查阅发现apt安装的版本过低&#xff1b; 安装meson sudo apt-get install python3 python3-pip …

LeetCode 111. 二叉树的最小深度

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 111. 二叉树的最小深度&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 二、…

程序的编译与链接——ARM可执行文件ELF

读书《嵌入式C语言自我修养》笔记 目录 读书《嵌入式C语言自我修养》笔记 ARM编译工具 使用readelf命令查看ELF Header 使用readelf命令查看ELF section header 程序编译 预处理器 编译器 &#xff08;1&#xff09;词法分析。 &#xff08;2&#xff09;语法分析。 …

班级人员可视化项目

页面分布文件分布index.html(搭建页面)index.css (修饰页面)fonts (放图标)images &#xff08;放图片&#xff09;jsjquery.js &#xff08;调整页面的js&#xff09;flexible.js (尺寸大小的js)echarts.min.js (charts图表的js)chinaMap…

论文投稿指南——中文核心期刊推荐(中国医学 2)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

普元PAS部署springboot项目访问500

背景 项目需要从东方通部署迁移到普元PAS部署。记录一下遇到的问题 问题一 WebSocket启动异常: Error creating bean with name ‘serverEndpoint’ defined in class path resource 因为SpringBoot默认使用的容器是tomcat 对应的Websocket实现 PAS中直接使用ServerEndpoin…

虹科新品丨什么是光纤微动开关?(下)

HK-Micronor光纤微动开关 HK-MR386光纤微动开关和HK-MR380系列控制器搭配使用&#xff0c;提供了一种全新的创新型信号解决方案&#xff0c;可以长距离部署在困难和危险环境中。该开关传感器采用光中断的方法&#xff0c;通过双工62.5/125μm光纤链路&#xff0c;进行可靠的信号…