【Linux】:多线程(POSIX 信号量 、基于环形队列的生产消费者模型)

📃个人主页:island1314  

🔥个人专栏:Linux—登神长阶

⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞

目录

1. POSIX 信号量 💌

 1.1 基本概念

 1.2 POSIX 信号量的常用函数

 1.3 信号量的使用步骤

(1)命名信号量

(2)无名信号量

 1.4 信号量的应用场景

 1.5 信号量的优缺点

🍉 优点

🍉 缺点

 1.6 信号量的案例

(1)命名信号量

(2)无名信号量

1.7 POSIX 信号量 VS System 信号量

1. 基本概念

2. 主要区别

2. 基于环形队列的生产消费模型 📚

2.1 基本思想

2.2 代码实现

3. 勉励 📖


1. POSIX 信号量 💌

信号量的本质是一个计数器,而申请信号量就是对资源的预订

 1.1 基本概念

🚀 POSIX信号量 和 SystemV信号量 作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

POSIX 信号量有两种:

  1. 命名信号量(Named Semaphore)
    • 可以在不同的进程间共享。
    • 通过名字(字符串)标识。
    • 使用 sem_opensem_close 函数管理。
  2. 无名信号量(Unnamed Semaphore)
    • 只能在线程或使用共享内存的进程间共享。
    • 通过 sem_initsem_destroy 函数管理。

 1.2 POSIX 信号量的常用函数

#include <semaphore.h>

① 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
    sem:把信号量的地址传进来
    pshared:0表示线程间共享,非零表示进程间共享
    value:信号量初始值  
② 销毁信号量 
int sem_destroy(sem_t *sem); 

③ 等待信号量
int sem_wait(sem_t *sem); //P()
功能:等待信号量,会将信号量的值减1
--就是对信号量进行申请,如果申请成功,该函数就会立即返回,并且代码继续往后走。如果不成功,即信号量不足了,就会被阻塞在这里。

④ 发布信号量
int sem_post(sem_t *sem); //V()
功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。

(1)命名信号量相关函数

函数功能
sem_open打开或创建一个命名信号量
sem_close关闭命名信号量
sem_unlink删除命名信号量
sem_waitP 操作:减少信号量值,如果信号量值为 0,则阻塞
sem_trywait非阻塞的 P 操作,如果信号量值为 0,直接返回错误
sem_postV 操作:增加信号量值,并唤醒阻塞的进程或线程
sem_getvalue获取当前信号量的值

(2)无名信号量相关函数

函数功能
sem_init初始化一个无名信号量
sem_destroy销毁一个无名信号量
sem_wait同命名信号量
sem_trywait同命名信号量
sem_post同命名信号量
sem_getvalue同命名信号量

 1.3 信号量的使用步骤

(1)命名信号量
1. 创建信号量:
 sem_t *sem = sem_open("/my_semaphore", O_CREAT, 0644, 1);
    "/my_semaphore" 是信号量的名字。
    O_CREAT 表示创建信号量。
    0644 是权限位。
    1 是信号量的初始值。

2. P 操作:
 sem_wait(sem);

3. V 操作:
 sem_post(sem);

4. 关闭和删除信号量:
 sem_close(sem); sem_unlink("/my_semaphore");
(2)无名信号量
1. 初始化信号量:
 sem_t sem; sem_init(&sem, 0, 1);
    &sem 是信号量指针。
    0 表示信号量用于线程间同步(1 表示进程间同步)。
    1 是信号量的初始值。

2. P 操作:
 sem_wait(&sem);

3. V 操作:
 sem_post(&sem);

4. 销毁信号量:
 sem_destroy(&sem);

 1.4 信号量的应用场景

  1. 限制资源访问数量

    • 使用信号量控制同时访问共享资源的线程或进程数量。例如,限制数据库连接池的最大连接数。
  2. 实现线程或进程间同步

    • 使用信号量实现某些线程或进程需要等待另一个线程或进程完成某些任务的场景。
  3. 生产者-消费者问题

    • 使用信号量控制生产者和消费者对缓冲区的访问。

 1.5 信号量的优缺点

🍉 优点
  • 简单高效,适合对共享资源的计数和同步。
  • 提供阻塞和非阻塞操作,灵活应对不同的编程需求。
🍉 缺点
  • 信号量值的调整依赖程序逻辑,容易出现误操作(如多次 postwait)。
  • 使用不当可能导致死锁或优先级反转问题。

 1.6 信号量的案例

(1)命名信号量
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>   // O_CREAT
#include <unistd.h>  // sleep

int main() {
    sem_t *sem = sem_open("/my_semaphore", O_CREAT, 0644, 1);

    if (sem == SEM_FAILED) {
        perror("sem_open failed");
        return 1;
    }

    printf("Waiting on semaphore...\n");
    sem_wait(sem);

    printf("Critical section\n");
    sleep(2);  // Simulate work

    printf("Exiting critical section\n");
    sem_post(sem);

    sem_close(sem);
    sem_unlink("/my_semaphore");

    return 0;
}
(2)无名信号量
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t sem;

void* thread_func(void* arg) {
    sem_wait(&sem);
    printf("Thread %d in critical section\n", *(int*)arg);
    sleep(1);  // Simulate work
    printf("Thread %d exiting critical section\n", *(int*)arg);
    sem_post(&sem);
    return NULL;
}

int main() {
    sem_init(&sem, 0, 1);

    pthread_t t1, t2;
    int id1 = 1, id2 = 2;

    pthread_create(&t1, NULL, thread_func, &id1);
    pthread_create(&t2, NULL, thread_func, &id2);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    sem_destroy(&sem);

    return 0;
}

🔥 POSIX 信号量提供了一种简单而灵活的机制来解决多线程或多进程编程中的同步问题。通过选择合适的信号量类型和正确使用信号量操作,可以高效管理共享资源的访问。

1.7 POSIX 信号量 VS System 信号量

🔥 POSIX 信号量 System V 信号量 是两种实现信号量的机制,都用于进程或线程间的同步,但它们在实现细节、功能和使用方式上存在显著差异

之前 System V 信号量我们在这篇博客里 【Linux】 IPC 进程间通信(三)(消息队列 & 信号量) 说过

1. 基本概念
特性POSIX 信号量System V 信号量
标准来源POSIX 标准(IEEE)System V IPC(UNIX 系统早期)
灵活性支持线程间和进程间同步仅支持进程间同步
实现方式提供命名信号量和无名信号量两种形式通过内核中维护的信号量集实现
2. 主要区别

(1)信号量的类型与使用范围

特性POSIX 信号量System V 信号量
命名信号量支持,通过 sem_open 创建命名信号量不支持
无名信号量支持,通过 sem_init 初始化不支持
线程间同步支持,可以直接用于线程同步(如 pthread不支持
进程间同步支持(通过共享内存实现)支持

(2)信号量的创建与标识

特性POSIX 信号量System V 信号量
标识方式命名信号量通过名字标识,无名信号量通过变量标识通过信号量集的 key 标识
创建函数sem_open(命名)或 sem_init(无名)semget

(3)操作方式

特性POSIX 信号量System V 信号量
P 操作(等待)sem_waitsemop(使用结构体数组描述操作)
V 操作(释放)sem_postsemop
非阻塞操作提供 sem_trywait无直接支持
获取信号量值sem_getvalue通过 semctl 查询

(4)信号量的创建与标识数

特性POSIX 信号量System V 信号量
易用性接口简单,易于学习和使用接口复杂,使用时需要多个步骤
性能较高,特别是无名信号量(内存中实现,无系统调用)较低,所有操作都涉及内核调用

(5)线程支持

特性POSIX 信号量System V 信号量
线程间同步支持,直接用于 pthread不支持,仅适用于进程间

(6)持久性

特性POSIX 信号量System V 信号量
持久性命名信号量在系统重启或进程退出后不持久信号量集在系统重启后仍存在,需手动删除
删除方法sem_unlink(命名信号量)semctl 的 IPC_RMID 标志删除信号量集

小结

特性POSIX 信号量System V 信号量
适用场景更适合现代多线程、多进程编程更适合早期进程间通信
性能较高,特别是无名信号量较低,所有操作涉及内核
灵活性支持线程同步,接口简单仅支持进程同步,接口复杂
持久性无持久性,信号量退出后即销毁支持持久性,信号量需显式删除

🔥 总体而言,POSIX 信号量在现代系统中更常用,特别是在需要线程间同步时;System V 信号量则逐渐减少使用,但在某些传统 UNIX 环境中仍然可见

2. 基于环形队列的生产消费模型 📚

2.1 基本思想

环形队列采用数组模拟,用模运算来模拟环状特性 

🔥 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态

实现思想:

  1. 默认:为空 或 为满,指向同一个位置
  2. 环形队列中存取的资源 应该是 空间(初始 N ) 和 数据 (初始 0)
  3. 我们把 空间 和 数据看作两个信号量
    1. sem_t data = 0;
    2. sem_t space = N;
  4. 对于每个位置 pos,当往后走的时候, pos ++, pos %= N
  5. 任何人访问临界资源之前, 都必须先申请信号量 (资源数目)
生产者
int tail = 0;
P(空间)

// 给生产者空间,放入数据
ring[tail] = data;
tail++;

V(数据)



消费者
int head = 0;
P(数据)

// 给生产者空间,放入数据
int data = ring[head];
head++;

V(空间)

还有一些极端情况

  1. 生产消费,同时访问同一个位置
    1. 为空时:保证生产者,原子性先生产
    2. 为满时:保证消费者,原子性的消费
    3. 这里的空满,就体现了 互斥 和 同步 的特点
  2. 如果不为空也不为满呢?
    1. 生产者 和 消费者此时一定不是同一个位置
    2. 此时 生产者 和 消费者 就可以同时进行并发执行
  3. 结论:这就相当于一个 追逐游戏
    1. 生产者无法把 消费者套一个圈
    2. 消费者无法超过生产者
    3. 同一个位置:互斥同步的
    4. 不在同一个位置:并发

2.2 代码实现

为了尽量少的调用原生库,我们这里自己的封装POSIX信号量的C++类Sem,主要功能是对信号量的初始化、销毁,以及提供P()V()操作

Sem.hpp

#pragma once

#include <semaphore.h>

namespace SemModule
{
    int defalutsemval = 1;
    class Sem
    {
    public:
        Sem(int value = defalutsemval):_init_value(1)
        {
            int n = ::sem_init(&_sem, 0, _init_value);
            (void)n;
        }

        void P()
        {
            int n = ::sem_wait(&_sem);
            (void)n;
        }

        void V()
        {
            int n = ::sem_post(&_sem);
            (void)n;
        }

        ~Sem()
        {
            int n = ::sem_destroy(&_sem);
            (void)n;
        }
    private:
        sem_t _sem;
        int _init_value;
    };
}

RingBuffer.hpp

#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>

#include "Sem.hpp"

namespace RingBufferModule
{
    using namespace SemModule;
    template <typename T>
    class RingBuffer
    {
    public:
        RingBuffer(int cap): _ring(cap), _cap(cap), _p_step(0), _c_step(0), _datasem(0), _spacesem(cap)
        {    
        }

        // 这里我们为什么没有做判断 ?
        // 原因:信号量,本身就是表示资源数目的,只要成功, 就一定会有, 不需要判断!! -> sem
        // 之前 if 判断那里是把资源当作整体来申请,但是不会去整体使用(局部使用)
        // 而且我们也不知道使用情况,所有需要在内部做判断
        void Equeue(const T &in)
        {
            // 生产者
            _spacesem.P();
            _ring[_p_step] = in; // 生产完毕
            _p_step++;
            _p_step %= _cap;     // 维持唤醒特性
            _datasem.V();
        }
        void Pop(T *out)
        {
            // 消费者
            _datasem.P();
            *out = _ring[_c_step]; // 预订
            _c_step++;
            _c_step %= _cap;
            _spacesem.V();
        }

        ~RingBuffer()
        {
        }
    private:
        std::vector<T> _ring;   // 环, 临界资源
        int _cap;               // 总容量
        int _p_step;            // 生产者位置
        int _c_step;            // 消费位置

        Sem _datasem;         // 数据信号量
        Sem _spacesem;        // 空间信号量
    };
}

 1. 类成员变量

  • _ring:一个 std::vector<T>,用来存储数据项,大小为 cap,实现环形缓冲。

  • _cap:表示缓冲区的总容量,即 RingBuffer 能存放的元素数量。

  • _p_step 和 _c_step:分别表示生产者和消费者的位置,循环移动(通过取模运算),实现环形结构。

  • _datasem 和 _spacesem:分别是用于管理数据和空间的信号量,_datasem 用于确保消费者有数据可消费,_spacesem 确保生产者有空间可生产。

2. 构造函数 RingBuffer(int cap) 与 析构函数 ~RingBuffer()

  • 初始化环形缓冲区 _ring,信号量 _datasem 和 _spacesem

  • _datasem 初始化为 0,表示缓冲区最初没有数据,消费者将被阻塞直到有数据。

  • _spacesem 初始化为 cap,表示缓冲区最初是空的,生产者可以生产最多 cap 个元素

  • 析构函数目前为空,因为信号量和 std::vector 会在对象销毁时自动清理资源。

3. 生产者方法 Equeue(const T &in)

  • P() 操作:在生产之前,生产者会等待缓冲区有空位(即 _spacesem)。

  • 数据插入:生产者将数据放入当前位置,更新 _p_step(并通过模运算使其循环)。

  • V() 操作:生产者发布一个数据信号量,通知消费者有新的数据可以消费。

4. 消费者方法 Pop(T *out)

  • P() 操作:消费者会等待数据的到来(即 _datasem)。

  • 数据消费:消费者从当前 _c_step 位置取数据并将 out 赋值。

  • V() 操作:消费者发布一个空位信号量,通知生产者可以生产新数据。

5. 信号量机制的设计

  • _spacesem:确保生产者不会超出缓冲区容量。每次生产时,P() 操作减少空位数,V() 操作则增加空位数,通知生产者生产。

  • _datasem:确保消费者不会在没有数据时消费。每次消费时,P() 操作减少数据数量,V() 操作则增加数据数量,通知消费者消费。

6. 为什么不需要判断资源是否充足

在信号量的使用中,sem_waitP())会在资源不足时阻塞调用线程,而不是简单地返回错误。因此无需额外判断资源是否充足,信号量本身处理了资源的等待和唤醒

这个 RingBuffer 类是典型的生产者-消费者模式实现,使用了信号量来确保生产者和消费者之间的同步:

  • 生产者在缓冲区有空间时才能生产,并通知消费者有数据可以消费。

  • 消费者在缓冲区有数据时才能消费,并通知生产者有空位可以生产。 

Main.cc -- 测试代码

#include "RingBuffer.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <functional>

using namespace RingBufferModule;

void *Consumer(void *args)
{
    RingBuffer<int> *rb = static_cast<RingBuffer<int> *> (args);
    while(true)
    {
        sleep(1);
        // 1. 消费数据
        int data;
        rb->Pop(&data);

        // 2. 处理: 花时间
        std::cout << "消费了一个数据: " << data << std::endl;
    }
}

void *Productor(void *args)
{
    RingBuffer<int> *rb = static_cast<RingBuffer<int> *> (args);
    int data = 0;
    while(true)
    {
        // 1. 获取数据: 花时间
        //sleep(1);
        rb->Equeue(data);

        // 2. 生产数据
        std::cout << "生产了一个数据: " << data << std::endl;
        data++;
    }
}


int main()
{
    RingBuffer<int> *rb = new RingBuffer<int>(5); // 共享资源-> 临界对象

    // 单生产, 单消费
    pthread_t c1, p1;
    pthread_create(&c1, nullptr, Consumer, rb);
    pthread_create(&p1, nullptr, Productor, rb);

    pthread_join(c1, nullptr);
    pthread_join(p1, nullptr);
    
    delete rb;

    return 0;
}

当然我们也可以进行多生产多消费如下:

int main()
{
    RingBuffer<int> *rb = new RingBuffer<int>(5);

    // 单生产, 单消费
    pthread_t c1, p1, c2, p2, c3;
    pthread_create(&c1, nullptr, Consumer, rb);
    pthread_create(&p1, nullptr, Productor, rb);
    pthread_create(&c2, nullptr, Consumer, rb);
    pthread_create(&p2, nullptr, Productor, rb);
    pthread_create(&c3, nullptr, Consumer, rb);

    pthread_join(c1, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(p2, nullptr);
    pthread_join(c3, nullptr);
    
    delete rb;

    return 0;
}

为了让我们的 RingBuffer.hpp 原生东西更少,我们调用之前实现的 Mutex.hpp,Mutex.hpp 具体可以看之前【多线程(互斥 &&  同步)】博客,此时的 RingBuffer.hpp 代码如下:

#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>

#include "Sem.hpp"
#include "Mutex.hpp"

namespace RingBufferModule
{
    using namespace SemModule;
    using namespace LockModule;

    template <typename T>
    class RingBuffer
    {
    public:
        RingBuffer(int cap): _ring(cap), _cap(cap), _p_step(0), _c_step(0), _datasem(0), _spacesem(cap)
        {
        }

        void Equeue(const T &in)
        {
            // 生产者
            _spacesem.P();
            {
                LockGuard lockguard(_p_lock);
                _ring[_p_step] = in; // 生产完毕
                _p_step++;
                _p_step %= _cap;     // 维持唤醒特性
            }
            _datasem.V();
        }
        void Pop(T *out)
        {
            // 消费者
            _datasem.P();
            {
                LockGuard lockguard(_c_lock);
                *out = _ring[_c_step]; // 预订
                _c_step++;
                _c_step %= _cap;
            }
            _spacesem.V();
        }

        ~RingBuffer()
        {
        }

    private:
        std::vector<T> _ring;   // 环, 临界资源
        int _cap;               // 总容量
        int _p_step;            // 生产者位置
        int _c_step;            // 消费位置

        Sem _datasem;         // 数据信号量
        Sem _spacesem;        // 空间信号量

        Mutex _p_lock;
        Mutex _c_lock;

    };
}

3. 勉励 📖

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果有不懂和发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【Linux】的内容,请持续关注我 !!

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

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

相关文章

人工智能的历史概况和脉络

人工智能( AI ) 的历史始于古代&#xff0c;当时有神话、故事和谣言称&#xff0c;人工生物被工匠大师赋予了智慧或意识。从古代到现在&#xff0c;对逻辑和形式推理的研究直接导致了20 世纪 40 年代可编程数字计算机的发明&#xff0c;这是一种基于抽象数学推理的机器。这种设…

昇思25天学习打卡营第33天|共赴算力时代

文章目录 一、平台简介二、深度学习模型2.1 处理数据集2.2 模型训练2.3 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。…

基于32单片机的RS485综合土壤传感器检测土壤PH、氮磷钾的使用(超详细)

1-3为RS485综合土壤传感器的基本内容 4-5为基于STM32F103C8T6单片机使用RS485传感器检测土壤PH、氮磷钾并显示在OLED显示屏的相关配置内容 注意&#xff1a;本篇文件讲解使用的是PH、氮磷钾四合一RS485综合土壤传感器&#xff0c;但里面的讲解内容适配市面上的所有多合一的RS…

Vue入门到精通:运行环境

Vue入门到精通&#xff1a;运行环境 Vue3的运行环境搭建主要有两种方法&#xff1a;一种是直接在页面中引入Vue库&#xff0c;另一种是通过脚手架工具创建Vue项目。 &#xff08;一&#xff09;页面直接引入Vue库 页面直接引入Vue库的方法&#xff0c;是指在HTML网页中通过s…

PHP项目从 php5.3 版本升级到 php8.3 版本时的一些问题和解决方法记录

一个原来的项目&#xff0c;因为业务需要&#xff0c;进行了PHP版本升级&#xff0c;从php5.3直接升级到php8.3。变化挺大的&#xff0c;原程序中有很多不再兼容&#xff0c;在此处进行一下记录。 一、Deprecated: 显式转换问题 报错内容&#xff1a;Deprecated: Implicit con…

【Python篇】PyQt5 超详细教程——由入门到精通(序篇)

文章目录 PyQt5 超详细入门级教程前言序篇&#xff1a;1-3部分&#xff1a;PyQt5基础与常用控件第1部分&#xff1a;初识 PyQt5 和安装1.1 什么是 PyQt5&#xff1f;1.2 在 PyCharm 中安装 PyQt51.3 在 PyCharm 中编写第一个 PyQt5 应用程序1.4 代码详细解释1.5 在 PyCharm 中运…

Apache Kylin最简单的解析、了解

官网&#xff1a;Overview | Apache Kylin 一、Apache Kylin是什么&#xff1f; 由中国团队研发具有浓厚的中国韵味&#xff0c;使用神兽麒麟&#xff08;kylin&#xff09;为名 的一个OLAP多维数据分析引擎:&#xff08;据官方给出的数据&#xff09; 亚秒级响应&#xff…

【工具】linux matlab 的使用

问题1 - 复制图表 在使用linux matlab画图后&#xff0c;无法保存figure。 例如在windows下 但是在linux下并没有这个“Copy Figure”的选项。 这是因为 “ The Copy Figure option is not available on Linux systems. Use the programmatic alternative.” 解决方案&…

opencv——(图像梯度处理、图像边缘化检测、图像轮廓查找和绘制、透视变换、举例轮廓的外接边界框)

一、图像梯度处理 1 图像边缘提取 cv2.filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]]) 功能&#xff1a;用于对图像进行卷积操作。卷积是图像处理中的一个基本操作&#xff0c;它通过一个称为卷积核&#xff08;或滤波器&#xff09;的小矩阵在图像上…

Redis - 集合 Set 及代码实战

Set 类型 定义&#xff1a;类似 Java 中的 HashSet 类&#xff0c;key 是 set 的名字&#xff0c;value 是集合中的值特点 无序元素唯一查找速度快支持交集、并集、补集功能 常见命令 命令功能SADD key member …添加元素SREM key member …删除元素SCARD key获取元素个数SI…

androidstudio导入项目至成功运行(保姆级教程)

目录 一、下载资源包 二、创建一个新的项目 三、替换配置文件 四、打开Android Studio 一、下载资源包 将你得到的资源包下载到你能够找到的位置然后解压&#xff0c;方便操作 二、创建一个新的项目 如果已经有新创建的项目就不用新建了&#xff0c;但是需要注意的是&am…

SpringBoot SPI

参考 https://blog.csdn.net/Peelarmy/article/details/106872570 https://javaguide.cn/java/basis/spi.html#%E4%BD%95%E8%B0%93-spi SPI SPI(service provider interface)是JDK提供的服务发现机制。以JDBC为例&#xff0c;JDK提供JDBC接口&#xff0c;在包java.sql.*。MY…

linux部署ansible自动化运维

ansible自动化运维 1&#xff0c;编写ansible的仓库&#xff08;比赛已经安装&#xff0c;无需关注&#xff09; 1、虚拟机右击---设置---添加---CD/DVD驱动器---完成---确定 2、将ansible.iso的光盘连接上&#xff08;右下角呈绿色状态&#xff09; 3、查看光盘挂载信息 df -h…

Java——网络编程(中)—TCP通讯(上)

一 (网络编程常用类) (Java为了跨平台&#xff0c;在网络应用通信时是不允许直接调用操作系统接口的&#xff0c;而是由java.net包来提供网络功能。下面来介绍几个java.net包中的常用的类) 1 InetAddress类的使用 (封装计算机的IP地址和DNS) (这个类没有构造方法——>如…

SQL server学习05-查询数据表中的数据(上)

目录 一&#xff0c;基本格式 1&#xff0c;简单的SQL查询语句 2&#xff0c;关键字TOP 3&#xff0c;关键字DISTINCT 二&#xff0c;模糊查询 1&#xff0c;通配符 三&#xff0c;对结果集排序 1&#xff0c;不含关键字DISTINCT 2&#xff0c;含关键字DISTINCT 3&…

Gitlab ci/cd

关于gitlab ci/cd&#xff0c;就是实现DevOps的能力&#xff0c;即Development &Operations的缩写&#xff0c;也就是开发&运维。CI/CD 指的是软件开发的持续集成方法&#xff0c;我们可以持续构建、测试和部署软件。通过持续方法的迭代能使得我们减少在错误代码或者错…

Leetcode42-环形链表

题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使…

计算机网络错题

文章目录 码分复用透明传输差错检测停止-等待协议回退N帧协议CSMA/CD协议以太网交换机Vlanip地址的无分类编制方法ip地址的应用规划ip数据包的发送和转发过程路由信息协议IPI2016201720202022 2.5信道 码分复用 透明传输 差错检测 停止-等待协议 回退N帧协议 CSMA/CD协议 以太网…

改进系列(5):在ResNet网络添加SelfAttention自注意力层实现的遥感卫星下的土地利用情况图像分类

目录 1. ResNet介绍 2. SelfAttention 层 3. ResNet34 SelfAttention 4. 遥感卫星下的土地使用情况分类 4.1 土地使用情况数据集 4.2 训练 4.3 训练结果 4.4 推理 1. ResNet介绍 ResNet&#xff08;残差网络&#xff09;是一种深度卷积神经网络模型&#xff0c;由Ka…

ARM循环程序和子程序设计

1、计算下列两组数据的累加和并存入到sum1和 sum2 单元中。datal:0x12,0x935,0x17,0x100,0x95,0x345。 data2:0x357,0x778,0x129,0x188,0x190,0x155,0x167。 1.定义数据段 ;定义数据段&#xff0c;类型为data(表示为数据段)&#xff0c;权限为可读可写(程序可以读取和修改这…