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

目录

概述

代码结构

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队列。此外,结合内存管理优化和缓存友好性设计,可以进一步提升队列的性能和效率。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决mp框架无法更新null值的问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、解决方法 前言 在使用mp框架过程中可能会遇到需要将某个字段为null的情况 但是mp自带的方法例如update默认是不支持更新null值的 一、解决方法 主要是这两…

分析图形学示例报告

一、实验任务 二、主要功能模块 三、代码 //自定义坐标系模块 CDC* pDC GetDC();//获得设备上下文 CRect rect;//定义矩形 GetClientRect(&rect);//获得矩形客户去大小 pDC->SetMapMode(MM_ANISOTROPIC);//自定义坐标系 pDC->SetWindowExt(rect.Width()/4, rect.He…

windows server2012 配制nginx安装为服务的时候,直接跳要安装.net框架,用自动的安装,直接失败的解决。

1、上一个已成功在安装过程中的图&#xff1a; 2、之前安装过程中错误的图&#xff1a; 3、离线安装解决&#xff1a; 下载.net framework 3.5&#xff0c;然后解压后&#xff0c;选择指定备用源路径&#xff0c;然后选择.net安装包所在目录&#xff1a; 只要指定上面全路径就…

Kubernetes 常用指令2

kubernetes 常用命 令 1. 编写 yaml 文件 2. kubectl create 通过配置文件名或标准输入创建一个集群资源对象&#xff0c;支 持 json 和 yaml 格式的文件 语法&#xff1a; kubectl create -f 文件名 kubectl create deployment 资源名 --image镜像名 kubectl create deplo…

Halcon 深度学习 分类预处理

文章目录 read_dl_dataset_classification 产生一个深度学习数据集算子split_dl_dataset 将样本分为训练、验证和测试子集create_dl_preprocess_param 使用预处理参数创建字典preprocess_dl_dataset 预处理DLDataset中声明的整个数据集write_dict 写入字典文件find_dl_samples …

Java wrapperr打包springboot项目到linux和Windows

Java wrapper打包springboot项目到linux和Windows 1 Java wrapper 说明2 linux的安装步骤2.1 解压 创建目录2.2 复制文件2.3 配置文件2.4 启动 3 windows3.1 配置文件3.2 复制文件3.3 启动 1 Java wrapper 说明 前提&#xff1a; 一定要有Java环境&#xff08;我使用的是jdk1.8…

C和指针:指针

内存和地址 程序视角看内存是一个大的字节数组&#xff0c;每个字节包含8个位&#xff0c;可以存储无符号值0至255,或有符号值-128至127。 多个字节可以合成一个字&#xff0c;许多机器以字为单位存储整数&#xff0c;每个字一般由2个或4个字节组成。 由于它们包含了更多的位&…

油耳用什么掏耳朵比较好?可视挖耳勺推荐平价

掏耳朵是一个轻松又舒服的感觉&#xff0c;很多人就会用棉签和普通耳勺越掏越进&#xff0c;在盲掏的过程中容易弄伤耳膜。所以我们在掏耳时要选好工具。市面上的智能可视挖耳勺&#xff0c;顶端带有摄像头&#xff0c;可以通过清楚的观察到耳道中的情况。但现在市面上关于可视…

在Linux中从视频流截取图片帧(ffmpeg )

Linux依赖说明: 说明: 使用到的 依赖包 1. ffmpegsudo apt update sudo apt-get install ffmpeg2. imagemagick (选装) (检测图像边缘信息推断清晰度,如果是简单截取但个图像帧>用不到<)sudo apt-get install imagemagick备注: 指令及相关参数说明核心指令: (作用: 执…

VSCode好用的插件推荐

1. Chinese 将vscode翻译成简体中文 2. ESLint 自动检查规范 3. Prettier - Code formatter 可以自动调整代码的缩进、换行和空格&#xff0c;确保代码风格统一。通过配置&#xff0c;Prettier可以在保存文件时自动格式化代码 https://juejin.cn/post/74025724757198274…

从 Postgres 到 ClickHouse:数据建模指南

本文字数&#xff1a;7149&#xff1b;估计阅读时间&#xff1a;18 分钟 作者&#xff1a;Sai Srirampur 本文在公众号【ClickHouseInc】首发 上个月&#xff0c;我们收购了专注于 Postgres CDC 的 PeerDB。PeerDB 使得数据从 Postgres 复制到 ClickHouse 变得既快速又简单。Pe…

Docker日志管理之Filebeat+ELK日志管理

所需安装包及镜像 安装步骤 把所需镜像导入到Docker容器 打开/etc/sysctl.conf配置文件&#xff0c;添加参数 打开资源限制配置文件&#xff0c;添加参数 创建一个网络 在根目录下创建一个项目目录 创建Elasticsearch子目录 在项目目录下创建Elasticsearch子目录 将安装Elast…

微生物分类检测系统源码分享

微生物分类检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

突发!OpenAI发布最强模型o1:博士物理92.8分,IOI金牌水平

梦晨 衡宇 发自 凹非寺量子位 | 公众号 QbitAI 参考ChatGPT&#xff1a;点击使用 来了来了&#xff01;刚刚&#xff0c;OpenAI新模型无预警上新&#xff1a; o1系列&#xff0c;可以进行通用复杂推理&#xff0c;每次回答要花费更长时间思考。 在解决博士水平的物理问题时&a…

Notepad++插件:TextFX 去除重复行

目录 一、下载插件 TextFX Characters 二、去重实操 2.1 选中需要去重的文本 2.2 操作插件 2.3 结果展示 2.3.1 点击 Sort lines case sensitive (at column) 2.3.2 点击 Sort lines case insensitive (at column) 一、下载插件 TextFX Characters 点【插件】-【插件管理…

【OpenAPI】Spring3 集成 OpenAPI 生成接口文档

Spring3 集成 OpenAPI 生成接口文档 1. 依赖 Spring 版本&#xff1a;3.0.5 Java 版本&#xff1a;jdk21 OpenAPI 依赖&#xff1a; <!-- https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-starter-webmvc-ui --> <dependency><groupI…

如何通过 PhantomJS 模拟用户行为抓取动态网页内容

引言 随着网页技术的不断进步&#xff0c;JavaScript 动态加载内容已成为网站设计的新常态&#xff0c;这对传统的静态网页抓取方法提出了挑战。为了应对这一挑战&#xff0c;PhantomJS 作为一个无头浏览器&#xff0c;能够模拟用户行为并执行 JavaScript&#xff0c;成为了获…

GeoPandas在地理空间数据分析中的应用

GeoPandas是一个开源的Python库&#xff0c;专门用于处理和分析地理空间数据。它建立在Pandas库的基础上&#xff0c;扩展了Pandas的数据类型&#xff0c;使得用户能够在Python中方便地进行GIS操作。GeoPandas的核心数据结构是GeoDataFrame&#xff0c;它是Pandas的DataFrame的…

uniapp小程序,使用腾讯地图获取定位

本篇文章分享一下在实际开发小程序时遇到的需要获取用户当前位置的问题&#xff0c;在小程序开发过程中经常使用到获取定位功能。uniapp官方也提供了相应的API供我们使用。 官网地址&#xff1a;uni.getLocation(OBJECT)) 官网获取位置的详细介绍这里就不再讲述了&#xff0c;大…

红光一字激光器在工业中的性能指标怎样

红光一字激光器作为现代工业中不可或缺的重要设备&#xff0c;以其独特的性能和广泛的应用场景&#xff0c;成为众多行业的首选工具。本文就跟大家详细探讨红光一字激光器在工业中的性能指标&#xff0c;以及这些指标如何影响其在实际应用中的表现。 光束质量 红光一字激光器以…