【Linux】-- POSIX信号量

news2024/9/29 18:06:55

目录

POSIX信号量

sem_init - 初始化信号量

sem_destroy - 销毁信号量

sem_wait - 等待信号量(P操作)

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

数据结构 - 环形结构

实现原理


POSIX信号量

#问:什么是信号量?

1. 共享资源 -> 任何一个时刻都只有一个执行流在进行访问 -> 临界资源、临界区的概念。

#问:如果一个共享资源,不当做一个整体,而让不同的执行流访问不同的区域的话,那么不就可以继续并发了吗?

        是的,当只有访问同一个资源的时候,我们在进行同步或者互斥。

        以此做到大部分依旧是并发,小部分情况下是互斥、同步。这样就可以更大力度的提高线程的效率。

根据以上模型:
1. 问:怎么知道一共有多少个资源?中途还剩多少资源?

2. 问:怎么保证这个资源就是给我们的?我们怎么知道我们一定可以具有一个共享资源?

2. 电影院的例子

买票的本质:

        资源(座位)的预定机制 —— 代表我们预定了资源(座位),其一定会为我们留这一个资源(座位),别人不会抢,也没法抢,因为持有信号量(票)。

信号量的本质: 

        是一个计算器,访问临界资源的时候,必须先申请信号量资源(sem--,预定资源,p),使用完毕信号量资源(sem++,释放资源,v)

#问:我们想申请资源,我们需要先申请信号量(sem--),如果我们申请完信号量,但是我们不去访问呢?

        不会出任何问题,对应的信号量资源中一定会有其一个。比如有6个信号,6个线程申请了,5个线程执行,1个不执行。此时再来1个线程(第7个),想申请信号量,不行申请不成功。因为此时信号量已经--到0了,不能再申请了。不访问但资源也会对应的留着。

如何理解信号量的使用:

        我们申请了一个信号量 -> 当前执行流一定具有一个资源,可以被它使用 -> 是哪一个志愿,需要程序员结合场景,自定义编码完成。

解决前面的问题:

1. 问:怎么知道一共有多少个资源?中途还剩多少资源?

        因为信号量初始化的时候初始为几,就代表一共多少资源。还剩多少资源取决于信号量在被使用期间,信号量的值剩几就是几。

2. 问:怎么保证这个资源就是给我们的(程序员编码)?我们怎么知道我们一定可以具有一个共享资源(信号量)?

         需要程序员结合场景,自定义编码完成的。

         信号量本质是一个计数器,只要申请成功了,其是对于临界资源的预定机制,只要预定成功就一定会有对应的资源。


sem_init - 初始化信号量

#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
        sem:信号量。
        pshared:0表示线程间共享,非零表示进程间共享。
        value:信号量初始值。
返回值:
  • 成功时返回0。
  • 出现错误时,返回-1,并设置errno以指示错误。

sem_destroy - 销毁信号量

#include <semaphore.h>
// 销毁信号量
int sem_destroy(sem_t *sem);
参数:
        sem:信号量。
返回值:
  • 成功时返回0。
  • 出现错误时,返回-1,并设置errno以指示错误。

sem_wait - 等待信号量(P操作)

// 申请信号量资源,申请成功,继续往后走。申请不成功,默认阻塞。
int sem_wait(sem_t *sem);

// 申请信号量资源,申请成功,继续往后走。申请不成功,立马返回(errno设置为EAGAIN)。
int sem_trywait(sem_t *sem);

// 指定特定时间abs_timeout,在该时间段内,未申请成功,挂起等待,超过时间成功,立马返回(errno设置为EAGAIN)。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
#include <semaphore.h>
// 等待信号量,会将信号量的值--
int sem_wait(sem_t *sem);
参数:
        sem:信号量。
返回值:
  • 成功时返回0。
  • 出现错误时,返回-1,并设置errno以指示错误。

sem_post - 发布信号量(V操作)

#include <semaphore.h>
//发布信号量,表示资源使用完毕,可以归还资源了。将信号量值++
int sem_post(sem_t *sem);
参数:
        sem:信号量。
返回值:
  • 成功时返回0。
  • 出现错误时,返回-1,并设置errno以指示错误。

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

数据结构 - 环形结构

        有环形结构的实现,有链表的实现、线性数组的实现等,此处采取线性数组的实现。

利用线性数组队列的物理结构数据结构,实现逻辑结构上的环形队列

  • 物理结构:在计算机里,真实的结构的形式存在。
  • 逻辑结构:程序员看待这个结构的方式。

逻辑结构的意义:
        物理结构不便于思考,不便于实现各种逻辑。锁以使用软件封装,将物理结构变为逻辑结构,然后基于逻辑结构展开思考。

对于基于环形队列的生产消费模型重点不是判断是否循环一圈的,if (start == end)判断,而是:

  1. 判空
  2. 判满

        因为if (start == end)无法判断空、满,所以不可使用。所以单纯的生产一个,往后移动一个,这个方法是很不好的。

环形队列的生产消费模型中,常见的判空、判满的方式有两种:

1. 计数器:
        0表示空,n表示满。

2. 专门浪费一个格子:

        当前格子的下一个格子,进行index %= n;index == 0;即当前格子不放数据,并且此时判断环形队列。

为空:消费与生产在一个格子。

为满:生产的下标+1再%n等于消费。

实现原理

        这个环形结构,既是生产者的生产结构,也是消费者的消费结构。所以,势必是生产与消费的共享资源。并且是多线程下的共享资源,就势必要考虑多线程访问的线程安全问题,以及多线程之间同步和互斥的问题。如果我们添加使用之前的互斥锁、条件变量的方案,有数据就让消费者消费,环形队列没有满就让环形队列生产。是可以实现多线程协同的,就是因为环形队列是和基于阻塞队列的生产消费模型一样的,是也有空、也有满。所以使用基于阻塞队列的生产消费模型一样的加锁也是可以的。

        但是就会有一个问题,如果用加锁方案,潜台词就是:在环形结构当中,如果加锁,我们是将环形结构看作整体使用。

需要注意的关键问题:

  • 为空:消费者不能消费,因为对应的环形结构里根本就没有任务。
  • 为满:生产者不能生产,因为会将环形结构里未被使用的任务覆盖。

        所以,为空的时候是不期望消费者运行的,为满的时候是不期望生产者运行的。

如果生产和消费指向了环形结构的同一个位置,就代表一定为空 / 为满

  • 从数据结构视角:就是要么为空,要么为满。
  • 从多线程视角:生产和消费要有互斥或者同步问题。
    • 在任意时刻,无论是消费者,还是生产者只能有一个在跑,是互斥的,互斥的运行需要同步的问题。

生产和消费指向同一个位置是小概率事件,大概率生产和消费都指向的是不同的位置。

  • 想让当生产和消费指向同一个位置,具有互斥同步关系就可以了。
  • 想让当生产和消费不指向同一个位置,让它们并发执行。

        也就是说,满的时候,消费与生产在同一个位置,这个时候互斥同步关系,生产者不能再执行,只能消费者执行 —— 生产者不能把消费者套一个圈的。对于空的时候同理。

期望:

  • 生产者不能将消费者套圈。
  • 消费者不能超过生产者,永远就相当于一个跟随者的方式。

实现:

  • 为空:一定要让生产者运行。
  • 为满:一定要让消费者运行。
  • 其他情况:可以并发访问。

#问:如何引入信号量?

        信号量是用来描述临界区中临界资源的一个计数器。所以在信号量的视角,就是将生产者与消费者最关心的资源,来进行计数,来使用信号量描述资源数量。因为为空、为满就是判断资源数目。

  • 生产者:最关注的是环形结构中的空间资源(有没有空间放数据)—>(信号量)spaceSem —> 起始N。
  • 消费者:最关注的是环形结构中的数据资源(有没有数据可消费)—>(信号量)dataSem —> 起始0。

操作:

  • 生产:
    • 一个生产者想生产,生产就需要有空间,就需要先进行对空间预约,即申请信号量(P操作)

    • 生产者将数据生产到环形队列的特定位置,生产者将数据生产了,将数据放入进去了。于是,生产者去生产下一个位置了,但是当前位置是依旧被占用的。所以生产者不能归还空间资源,于是V的是dataSam
  • 消费:
    • 一个消费者想消费,消费就需要有数据,就需要先进行对数据预约,即申请信号量(P操作)

    • 消费者将数据数据拿走了,于是这个数据曾经占用的空间资源就空出来了,于是V的是spaceSam

#问:当生产者与消费者同时在一个位置的时候,如何保证的谁先执行?

        以开始为例:生产者与消费者同时进行运行的时候,一定要先进行初始化信号量,于是spaceSem为N,dataSem为0。于是接着同时进行运行,就各自执行P操作,消费者一看dataSem为0申请不出来,于是申请失败,消费线程直接被挂起。只能等生产者先生产。

  • 为空:spaceSem为N,dataSem为0。只有生产者能执行。
  • 为满:spaceSem为0,dataSem为N。只有消费者能执行。

        以此,保证在同一个位置的时候,生产和消费的步调是互斥同步的。并且也以此保证了:

  • 生产者永远一定不会将消费者套圈。
  • 消费者永远一定不会超过生产者,永远就相当于一个跟随者的方式。

        信号量,就帮我们解决了,这一系列的问题。

Sem.hpp

        封装的信号量。

#ifndef _SEM_HPP_
#define _SEM_HPP_

#include <iostream>
#include <semaphore.h>

class Sem
{
public:
    Sem(int value)
    {
        sem_init(&sem_, 0, value);
    }

    ~Sem()
    {
        sem_destroy(&sem_);
    }

    void p()
    {
        sem_wait(&sem_);
    }

    void v()
    {
        sem_post(&sem_);
    }

private:
    sem_t sem_;
};

#endif

ringQueue.hpp

#ifndef _Ring_QUEUE_HPP_
#define _Ring_QUEUE_HPP_

#include <iostream>
#include <vector>
#include <pthread.h>
#include "Sem.hpp"

const int g_dafault_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(const int default_num = g_dafault_num)
        : ring_queue_(default_num)
        , num_(default_num)
        , p_step_(0)
        , c_step_(0)
        , space_sem_(default_num)
        , data_sem_(0)
    {}
    
    ~RingQueue()
    {}

    // 生产者: 空间资源
    void push(const T &in)
    {
        // 申请空间资源 - 空间少一个
        space_sem_.p();

        // 100%拿到了空间资源
        ring_queue_[p_step_++] = in;
        p_step_ %= num_;

        // 使用后空间后,数据多一个
        data_sem_.v();
    }

    // 消费者:数据资源
    void pop(T *out)
    {
        // 申请胡数据资源 - 数据少一个
        data_sem_.p();

        // 100%拿到了数据资源
        *out = ring_queue_[c_step_++];
        c_step_ %= num_;

        // 使用后数据后,空间多一个
        space_sem_.v();
    }

private:
    std::vector<T> ring_queue_;
    int num_;
    int c_step_; // 消费下标
    int p_step_; // 生产下标
    Sem space_sem_;
    Sem data_sem_;
};

#endif

testWain.cc

#include "ringQueue.hpp"
#include <iostream>
#include <ctime>
#include <cstdlib>

//消费者
void *consumer(void *args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while(true)
    {
        int x;
        // 1. 从环形队列中获取任务或者数据
        rq->pop(&x);

        // 2. 进行一定的处理 -- 不要忽略它的时间消耗问题
        std::cout << "消费:" << x << std::endl;
    }
    return nullptr;
}

// 生产者
void *producer(void *args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while(true)
    {
        int x = rand()%100 + 1;
        // 1. 构建数据或者任务对象 -- 一般是可以从外部来 -- 不要忽略它的时间消耗问题
        rq->push(x);

        // 2. 推送到环形队列中
        std::cout << "生产:" << x << std::endl;
    }
    return nullptr;
}

int main()
{
    srand((uint64_t)time(nullptr));
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void*)rq);
    pthread_create(&p, nullptr, producer, (void*)rq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);
    return 0;
}

将消费者放慢:

        我们可以通过将消费放慢,于是可以看到生产者瞬间生产满,然后由于信号量space_sum_ = 0;所以无法申请到空间资源,于是阻塞等待。然后就是消费一个,就有空间了,于是立马又生产一个,然后又阻塞等待消费者消费。

        对于单生产、单消费,由于有信号量的存在,所以对于同一个位置的生产、消费,不用担心他们会对同一个位置并发访问。为满,空间资源不就绪。为空,数据资源不就绪。所以一定有一方竞争失败,根本不用担心并发访问。

#问:如何在当前的代码下实现多生产,多消费?  

        这个时候我们在一个关系:生产者与消费者,的关系上新增了两个:生产者与生产者、消费者与消费者。

  • 生产者与生产者:竞争关系,互斥关系。
  • 消费者与消费者:竞争关系,互斥关系。

        所以,对于此的解决方式,是势必要使用加锁的。加两把锁:生产者与生产者一把、消费者与消费者一把。

#问:生产者们的临界资源是什么?消费者们的临界资源是什么?

        我们将环形队列对应的拆做了很多个小格子,生产者们消费者们都是竞争的这个小格子。而这个小格子的空间使用下标来标识。所以它们需要保护的是下标

        但是,要知道,加锁的区域是越小越好,而信号量是资源的预定机制,并且还一定是安全的(具有原子性)。那么:

#问:先加锁,还是先申请信号量?

        先申请信号量!

         因为,就相当于如果我们先加锁,如此去申请信号量的线程一定是很少的,而这个程序的工作效率是高还是低,是取决于我们将这个资源如何快速的派发给线程。

  • 如果先加锁:
    • 先申请锁,然后再申请信号量。然后全部做完、跑完了才能让下一个线程进来。
  • 如果先申请信号量:
    • 先申请信号量(先分资源的预定),最后哪怕只有一个线程进入到临界区中。虽然其他线程没有进入临界资源,但是可以并发的去竞争信号量。

就如同电影院:

  • 先到网上,每个人可以同时的去抢票,到时候直接看。
  • 看的时候,到电影院排队,然后买票,再看。

Sem.hpp

#ifndef _SEM_HPP_
#define _SEM_HPP_

#include <iostream>
#include <semaphore.h>

class Sem
{
public:
    Sem(int value)
    {
        sem_init(&sem_, 0, value);
    }

    ~Sem()
    {
        sem_destroy(&sem_);
    }

    void p()
    {
        sem_wait(&sem_);
    }

    void v()
    {
        sem_post(&sem_);
    }

private:
    sem_t sem_;
};

#endif

ringQueue.hpp

#ifndef _Ring_QUEUE_HPP_
#define _Ring_QUEUE_HPP_

#include <iostream>
#include <vector>
#include <pthread.h>
#include "Sem.hpp"

const int g_dafault_num = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(const int default_num = g_dafault_num)
        : ring_queue_(default_num), num_(default_num), p_step_(0), c_step_(0), space_sem_(default_num), data_sem_(0)
    {
        pthread_mutex_init(&c_lock_, nullptr);
        pthread_mutex_init(&p_lock_, nullptr);
    }

    ~RingQueue()
    {
        pthread_mutex_destroy(&c_lock_);
        pthread_mutex_destroy(&p_lock_);
    }

    // 生产者: 空间资源
    void push(const T &in)
    {
        // 申请空间资源 - 空间少一个
        space_sem_.p();

        // 加锁
        pthread_mutex_lock(&p_lock_); // 一定是竞争成功的生产者线程 -- 就一个!

        // 内部一定是单线程
        // 100%拿到了空间资源
        ring_queue_[p_step_++] = in;
        p_step_ %= num_;

        // 释放锁
        pthread_mutex_unlock(&p_lock_);

        // 使用后空间后,数据多一个
        data_sem_.v();
    }

    // 消费者:数据资源
    void pop(T *out)
    {
        // 申请胡数据资源 - 数据少一个
        data_sem_.p();

        // 加锁
        pthread_mutex_lock(&c_lock_); // 一定是竞争成功的消费者线程 -- 就一个!

        // 内部一定是单线程
        // 100%拿到了数据资源
        *out = ring_queue_[c_step_++];
        c_step_ %= num_;

        // 释放锁
        pthread_mutex_unlock(&c_lock_);

        // 使用后数据后,空间多一个
        space_sem_.v();
    }

private:
    std::vector<T> ring_queue_;
    int num_;
    int c_step_; // 消费下标
    int p_step_; // 生产下标
    Sem space_sem_;
    Sem data_sem_;
    pthread_mutex_t c_lock_; // 消费者的锁
    pthread_mutex_t p_lock_; // 生产者的锁
};

#endif

testMain.cc

#include "ringQueue.hpp"
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <unistd.h>

//消费者
void *consumer(void *args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while(true)
    {
        int x;
        // 1. 从环形队列中获取任务或者数据
        rq->pop(&x);

        // 2. 进行一定的处理 -- 不要忽略它的时间消耗问题
        std::cout << "消费: " << x << " [" << pthread_self() << "]" << std::endl;
    }
    return nullptr;
}

// 生产者
void *producer(void *args)
{
    RingQueue<int>* rq = (RingQueue<int>*)args;
    while(true)
    {
        int x = rand()%100 + 1;
        // 1. 构建数据或者任务对象 -- 一般是可以从外部来 -- 不要忽略它的时间消耗问题
        rq->push(x);

        // 2. 推送到环形队列中
        std::cout << "生产: " << x << " [" << pthread_self() << "]" << std::endl;
    }
    return nullptr;
}

int main()
{
    srand((uint64_t)time(nullptr));
    RingQueue<int> *rq = new RingQueue<int>();
    pthread_t c[3],p[2];
    pthread_create(c, nullptr, consumer, (void*)rq);
    pthread_create(c+1, nullptr, consumer, (void*)rq);
    pthread_create(c+2, nullptr, consumer, (void*)rq);

    pthread_create(p, nullptr, producer, (void*)rq);
    pthread_create(p+1, nullptr, producer, (void*)rq);

    for(int i = 0; i < 3; i++) pthread_join(c[i], nullptr);
    for(int i = 0; i < 2; i++) pthread_join(p[i], nullptr);
    return 0;
}

#问:多生产多消费的意义在哪里?

        是不是因为加锁,所以真正生产和真正消费也就只有一个线程?我们不能,也不要狭隘的认为,把任务或者数据放在交易场所,就是生产和消费了。我们将数据或者任务生产前和拿到之后处理,才是最耗费时间的。

  • 生产的本质:私有的任务-> 公共空间中
  • 消费的本质:公共空间中的任务-> 私有的

        虽然,生产任务、拿任务,都是一个一个的做的,但是处理任务的时候,是可以变为并发的。并发的生产数据,并发的处理数据。

        就像食堂的多个窗口:不是阿姨打菜、我们打饭然后就完了,而是阿姨做菜的时候,和我们吃饭的时候才是最耗费时间的。

#问:信号量本质是一把计数器 -> 计数器的意义是什么?

        计数器是用来表示,临界资源中的特定资源。在阻塞队列的生产者消费者模型中:申请锁 -> 判断与访问 -> 释放锁 -> 本质是我们并不清楚临界资源的情况!但是信号量是提前让程序员初始化好的计数器,也就是说:信号量要提前预设资源的情况,而且在pv变化过程中,我们可以在外部就能知晓临界资源的情况!

        计数器的意义:可以不用进入临界区,就可以得知资源情况,甚至可以减少临界区内部的判断!

        在没有信号量这个计数器的时候,因为不知道临界资源的状态,所以需要先加锁,再使用临界资源进行判断(条件变量),于是满了就释放锁并挂起。而有了信号量,因为信号量是资源的预定机制,其就是用来表明,环形队列中的情况。(在外部就可以得知临界区的状况)

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

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

相关文章

2. 驱动开发--驱动开发环境搭建

文章目录前言一、Linux中配置编译环境1.1 linux下安装软件的方法1.2 交叉编译工具链的安装1.2.1 测试是否安装成功1.3 设置环境变量1.3.1 将工具链导出到环境变量1.4 为工具链创建arm-linux-xxx符号链接二、 搭建运行开发环境2.1 tftp网络方式加载内核和设备树文件2.2 nfs网络方…

大事很妙,跨境电商用Reddit做营销做测评真的很有用

最近呢&#xff0c;东哥在和一个叫 jens 的海外社媒大佬聊天&#xff0c;聊起了Reddit&#xff0c;其实 Reddit 可是个不错的流量平台&#xff0c;里面有不少宝藏&#xff0c;跟我们国内的贴吧差不多啦。 作为美国热度排名前五的社交网站&#xff0c;流量如此不错的平台&#…

3、Improved Denoising Diffusion Probabilistic Models#

简介论文发现通过一些简单的修改&#xff0c;ddpm也可以在保持高样本质量的同时实现竞争对数可能性&#xff0c;反向扩散过程的学习方差允许以更少的正向传递数量级进行采样&#xff0c;而样本质量的差异可以忽略不计&#xff0c;这对于这些模型的实际部署非常重要。 github链接…

AOF:redis宕机,如何避免数据丢失

由于redis是基于内存的数据库&#xff0c;一旦宕机&#xff0c;数据就会丢失?如何解决&#xff1f; 目前&#xff0c;Redis 的持久化主要有两大机制&#xff0c;即 AOF&#xff08;Append Only File&#xff09;日志和 RDB&#xff08;Redis DataBase&#xff09; 快照。 AO…

SQL零基础入门学习(十四)

上篇&#xff1a;SQL零基础入门学习&#xff08;十三&#xff09; SQL NULL 值 NULL 值代表遗漏的未知数据。 默认地&#xff0c;表的列可以存放 NULL 值。 如果表中的某个列是可选的&#xff0c;那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录。这意味着该…

基于新一代kaldi项目的语音识别应用实例

本文是由郭理勇在第二届SH语音技术研讨会和第七届Kaldi技术交流会上对新一代kaldi项目在学术及“部署”两个方面报告的内容上的整理。如果有误&#xff0c;欢迎指正。 文字整理丨李泱泽 编辑丨语音小管家 喜报&#xff1a;新一代Kaldi团队三篇论文均被语音顶会ICASSP-2023接…

亿级高并发电商项目-- 实战篇 --万达商城项目 十三(编写购物车、优化修改商品、下架商品方法、购物车模块监听修改商品、删除商品消息)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

SSL证书对虚拟主机的用处有哪些?

虚拟主机是指在同一台服务器上&#xff0c;通过不同的域名或IP地址为多个网站提供服务的一种网络主机。而SSL证书则是一种数字证书&#xff0c;它用于加密网站与用户之间的通信&#xff0c;确保数据传输的安全性和完整性。在虚拟主机上&#xff0c;SSL证书有以下几个用处&#…

SQL Server2008详细安装步骤(保姆式教程)

安装包下载 链接&#xff1a;https://pan.baidu.com/s/1Rjx4DHJBeCW2asC_4Kzo6Q?pwdchui 提取码&#xff1a;chui 安装过程 1.解压后使用管理员身份打开安装程序 2.选择全新安装或向现有安装添加新功能 3.确认 4.输入产品密钥&#xff08;上方网盘安装包里有&#xff0…

【路径规划】基于前向动态规划算法在地形上找到最佳路径(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【ArcGIS Pro二次开发】(10):属性表字段(field)的修改

在ArcGIS Pro中&#xff0c;经常会遇到用字段计算器对要素的属性表进行计算。下面以一个例子演示如何在ArcGIS Pro SDK二次开发中实现。 一、要实现的功能 如上图所示的要素图层&#xff0c;要实现如下功能&#xff1a; 当字段【市级行政区】的值为【泉州市】时&#xff0c;将…

用 .NET 启动你的 DJI Ryze Tello 无人机

大疆的 DJI Ryze Tello 是入门级的无人机&#xff0c;不仅在 STEM 教育中有非常广泛的应用&#xff0c;也可以作为编程入门的首选。通过 UDP 协议调用 DJI Ryze Tello SDK 可以让 DJI Ryze Tello 无人机执行起飞&#xff0c;降落&#xff0c;转向以及不同的花式动作。本文将会通…

Parasoft的自动化测试平台到底强在哪?

在如今产品迭代如此之快的大背景下&#xff0c;软件测试这项工作越来越被大家所重视&#xff0c;但是通常情况下大家都是选择在产品上线前再去做测试&#xff0c;这个时候就会面临很多麻烦和挑战。首先&#xff0c;产品已经开发好之后&#xff0c;体量比较大&#xff0c;要从哪…

BurpSuite配置抓取HTTPS数据包

简介 我们在渗透测试的过程中&#xff0c;经常会遇到HTTPS的网站&#xff0c;Burp默认是没有办法抓取HTTPS的包的&#xff0c;想要让Burp抓取Https的包也很好办&#xff0c;只需要浏览器安装相关的证书即可&#xff0c;接下来将配置过程做一个记录。 前置条件&#xff1a; 1.J…

HashMap原理(一):哈希函数的设计

哈希函数的作用与本质 HashMap用来存储存在映射关系的数据对{key, value},在内部通过构造复合数据结构来封装数据对&#xff0c;即 //伪代码&#xff0c;非源码 class Pair<K, V> {public K key;public V value; }假设用来存储数据对的哈希数表为table&#xff0c;数据…

TCP状态转换

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 TCP状态转换专栏&#xff1a;《Linux从小白到大神》《网络编程》 TCP状态转换示意图如下 针对上面的示…

项目结束先别着急庆祝,项目经理还有这些事要做

项目管理生命周期结束阶段的目的是确认项目可交付成果的完成&#xff0c;使项目发起人满意&#xff0c;并向所有参与者和利益相关者传达项目的最终处置和状态。 项目结束确保项目的所有参与者和利益相关者都清楚后续活动&#xff08;如新项目、服务过渡、SLA等&#xff09;&a…

【ChatGPT情商大考验】ChatGPT教我谈恋爱

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

graph在细粒度分类中的应用

目录基于Graph-Propagation的相关性学习AAAI2020基于graph的高阶关系发现CVPR2021基于Graph-Propagation的相关性学习AAAI2020 来源&#xff1a;Graph-Propagation Based Correlation Learning for Weakly Supervised Fine-Grained Image Classification&#xff08;这或许是第…

MATLAB R2022b 安装教程

MATLAB R2022b 安装教程MathWorks 于2022年9月发布了 MATLAB 和 Simulink 产品系列的最新版本 Matlab R2022b版本 &#xff0c;加入两个新产品&#xff1a; Medical Imaging Toolbox — 可视化、配准、分割和标注二维及三维医学图像Simscape Battery — 设计和仿真电池和储能系…