【Linux多线程】信号量以及线程池

news2024/11/20 19:30:16

【Linux多线程】信号量以及线程池

目录

  • 【Linux多线程】信号量以及线程池
    • POSIX信号量
      • 基于环形队列的生产者消费者模型
    • 线程池

作者:爱写代码的刚子

时间:2024.4.2

前言:本篇博客将会介绍Linux线程中的信号量以及线程池,完善生产者消费者模型

POSIX(可移植操作系统接口)信号量和 System V 信号量是两种不同的 IPC(进程间通信)机制,用于在多个进程之间同步和共享资源。它们有一些区别,下面是它们的主要区别:

  1. API 和标准

    • POSIX 信号量:基于 POSIX 标准,通常在 Unix 系统上实现。
    • System V 信号量:System V IPC(Inter-Process Communication)是 Unix 系统上的一种 IPC 机制,其中包括信号量、消息队列和共享内存。
  2. 命名

    • POSIX 信号量可以命名,因此可以在不相关的进程之间共享。
    • System V 信号量通常是匿名的,只能由相关的进程共享。
  3. 使用方式

    • POSIX 信号量使用 sem_init()sem_wait()sem_post() 等函数进行操作。
    • System V 信号量使用 semget()semop()semctl() 等函数进行操作。
  4. 错误处理

    • POSIX 信号量的错误处理更为简单,通常通过返回值或者设置全局变量 errno 来表示错误。
    • System V 信号量的错误处理相对复杂,需要通过检查返回值和设置 errno 来判断错误。
  5. 删除

    • POSIX 信号量在系统关闭时会自动删除,不需要显式删除。
    • System V 信号量需要显式调用 semctl() 来删除。
  6. 可移植性

    • POSIX 信号量具有更好的可移植性,因为 POSIX 是一个标准,因此可以在不同的系统上使用相同的 API。
    • System V 信号量在不同的 Unix 系统之间的行为可能会有所不同。

POSIX信号量

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

信号量的本质是一把计数器(临界资源的数量),类似不等同于int cnt = n,描述临界资源中资源数量的多少。

  1. 申请计数器成功,就表示具有访问资源的权限了
  2. 申请了计数器资源,不代表当前访问了资源,申请了计数器资源是对资源的预定机制
  3. 计数器可以有效保证进入共享资源的执行流的数量

信号量只是限定进入共享资源执行流的数量,但是需要我们程序员来保证每个执行流访问自己的资源

信号量用来描述资源数目,把资源是否就绪放在了临界区之外,申请信号量时其实就间接的已经在做判断了

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value); 
参数:
	pshared:0表示线程间共享,非零表示进程间共享 
  value:信号量初始值

在这里插入图片描述

销毁信号量

int sem_destroy(sem_t *sem);

在这里插入图片描述

等待信号量

功能:等待信号量,会将信号量的值减1 
int sem_wait(sem_t *sem); //P()

在这里插入图片描述

发布信号量

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

在这里插入图片描述

上一篇生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序 (POSIX信号量):

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

  • 环形队列采用数组模拟,用模运算来模拟环形特征

在这里插入图片描述

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

在这里插入图片描述

  • 但是我们有信号量这个计数器,就很简单的进行多线程间的同步过程
  • 指向同一个位置,只能一个人访问:

空:生产者访问(P(生产者)关注还剩余多少空间

满:消费者访问(C(消费者)关注还剩余多少数据

所以我们需要两个信号量

  • 小demo实验

Main.cc

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>

const static int defaultcap = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)//申请信号量
    {
        sem_wait(&sem);

    }
    void V(sem_t &sem)//释放信号量
    {
        sem_post(&sem);
    }

public:
    RingQueue(int cap = defaultcap)
    :ringqueue_(cap),cap_(cap),c_step_(0),p_step_(0)
    {
        sem_init(&cdata_sem_,0,0);//第二个参数为0表示线程间共享,非0表示进程间共享
        sem_init(&cspace_sem_,0,cap);
    
    }

    //在环形队列为空或为满时能表现出局部的互斥特征
    void Push(const T&in)
    {
        P(cspace_sem_);
        ringqueue_[p_step_] = in;
        V(cdata_sem_);

        //位置后移,维持环形操作
        p_step_++;
        p_step_ %= cap_;

    }
    void Pop(T*out)
    {
        P(cdata_sem_);
        *out = ringqueue_[c_step_];
        V(cspace_sem_);

        c_step_++;
        c_step_%=cap_;
    }


    ~RingQueue(){
        sem_destroy(&cdata_sem_);
        sem_destroy(&cspace_sem_);
    }
private:
    std::vector<T> ringqueue_;
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem_;//消费者关心数据资源
    sem_t cspace_sem_;//生产者关心空间资源
};

RingQueue.hpp:

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>

const static int defaultcap = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)//申请信号量
    {
        sem_wait(&sem);

    }
    void V(sem_t &sem)//释放信号量
    {
        sem_post(&sem);
    }

public:
    RingQueue(int cap = defaultcap)
    :ringqueue_(cap),cap_(cap),c_step_(0),p_step_(0)
    {
        sem_init(&cdata_sem_,0,0);//第二个参数为0表示线程间共享,非0表示进程间共享
        sem_init(&cspace_sem_,0,cap);
    }

    //在环形队列为空或为满时能表现出局部的互斥特征
    void Push(const T&in)
    {
        P(cspace_sem_);
        ringqueue_[p_step_] = in;
        V(cdata_sem_);

        //位置后移,维持环形操作
        p_step_++;
        p_step_ %= cap_;

    }
    void Pop(T*out)
    {
        P(cdata_sem_);
        *out = ringqueue_[c_step_];
        V(cspace_sem_);

        c_step_++;
        c_step_%=cap_;
    }
    ~RingQueue(){
        sem_destroy(&cdata_sem_);
        sem_destroy(&cspace_sem_);
    }
private:
    std::vector<T> ringqueue_;
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem_;//消费者关心数据资源
    sem_t cspace_sem_;//生产者关心空间资源
};

在这里插入图片描述

注意,这个是在单生产者单消费者的情况下能够保证线程安全,因为生产者和消费者的下标表示是不同的,同时信号量在环形队列为空或为满时能表现出局部的互斥特征,但是在多生产者和多消费者的场景中可能会对同一个下标产生竞争,不能保证线程安全

改进代码使其满足多生产多消费的场景

生产者与消费者的互斥关系已经由信号量来维护了,但是生产者与生产者,消费者与消费者之间的互斥关系还是需要我们自己来维护!

我们采用先申请信号量再加锁理由:1. 信号量资源是不需要被保护的,它本身就是原子的 2. 如果先申请锁,那么申请锁和申请信号量就会变成串行,反之先申请信号量能一定程度上变成并行(提高并发量)

采用了环形队列,当队列不为空不为满时,生产者和消费者不会互相干扰,因为采用的是不同的下标,但是队列为空为满时,由信号量来维护生产者和消费者之间的关系(重点)

改进后的完整代码:

RingQueue.hpp:

#pragma once
#include <iostream>
#include <vector>
#include <semaphore.h>

const static int defaultcap = 5;

template <class T>
class RingQueue
{
private:
    void P(sem_t &sem)//申请信号量
    {
        sem_wait(&sem);

    }
    void V(sem_t &sem)//释放信号量
    {
        sem_post(&sem);
    }

    void Lock(pthread_mutex_t &mutex)
    {
        pthread_mutex_lock(&mutex);
    }

    void Unlock(pthread_mutex_t &mutex)
    {
        pthread_mutex_unlock(&mutex);
    }

public:
    RingQueue(int cap = defaultcap)
    :ringqueue_(cap),cap_(cap),c_step_(0),p_step_(0)
    {
        sem_init(&cdata_sem_,0,0);//第二个参数为0表示线程间共享,非0表示进程间共享
        sem_init(&cspace_sem_,0,cap);
    
        pthread_mutex_init(&c_mutex_,nullptr);
        pthread_mutex_init(&p_mutex_,nullptr);
    }

    //在环形队列为空或为满时能表现出局部的互斥特征
    void Push(const T&in)
    { 
        P(cspace_sem_);
        Lock(p_mutex_);
        ringqueue_[p_step_] = in;

        //位置后移,维持环形操作
        p_step_++;
        p_step_ %= cap_;
        Unlock(p_mutex_);

        V(cdata_sem_);

    }
    void Pop(T*out)
    {
        P(cdata_sem_);
        Lock(c_mutex_);
        *out = ringqueue_[c_step_];
        
        c_step_++;
        c_step_%=cap_;
        Unlock(c_mutex_);
        V(cspace_sem_);
    }
    ~RingQueue(){
        sem_destroy(&cdata_sem_);
        sem_destroy(&cspace_sem_);

        pthread_mutex_destroy(&c_mutex_);
        pthread_mutex_destroy(&p_mutex_);
    }
private:
    std::vector<T> ringqueue_;
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem_;//消费者关心数据资源
    sem_t cspace_sem_;//生产者关心空间资源

    pthread_mutex_t c_mutex_;
    pthread_mutex_t p_mutex_;
};

Task.hpp

#pragma once
#include <iostream>
#include <string>

std::string opers = "+-*/%";

enum
{
    DivZero = 1,
    ModZero,
    Unknown
};

class Task
{
public:
    Task(int x, int y, char op) : data1_(x), data2_(y), oper_(op), result_(0), exitcode_(0)
    {
    }
    Task(){}
    void run()
    {
        switch (oper_)
        {
            case '+':
                result_ = data1_ + data2_;
                break;
            case '-':
                result_ = data1_ - data2_;
                break;
            case '*':
                result_ = data1_ * data2_;
                break;
            case '/':
            {
                if (data2_ == 0)
                    exitcode_ = DivZero;
                else
                    result_ = data1_ / data2_;
            }
            break;
            case '%':
            {
                if (data2_ == 0)
                    exitcode_ = ModZero;
                else
                    result_ = data1_ % data2_;
            }
            break;
            default:
                exitcode_ = Unknown;
                break;
        }
    }
    void operator ()()
    {
        run();
    }
    std::string GetResult()
    {
         std::string r = std::to_string(data1_);
        r += oper_;
        r += std::to_string(data2_);
        r += "=";
        r += std::to_string(result_);
        r += "[code: ";
        r += std::to_string(exitcode_);
        r += "]";
        return r;
    }

    std::string GetTask()
    {
        std::string r = std::to_string(data1_);
        r+=oper_;
        r += std::to_string(data2_);
        r += "=?";
        return r;
    }

    ~Task()
    {}

private:
    int data1_;
    int data2_;
    char oper_;

    int result_;
    int exitcode_;
};

Main.cc

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include "RingQueue.hpp"
#include "Task.hpp"

using namespace std;

struct ThreadData
{
    RingQueue<Task> *rq;
    std::string threadname;
};

void *Productor(void *args)
{
    // sleep(3);
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    std::string name = td->threadname;
    int len = opers.size();
    while (true)
    {
        // 1. 获取数据
        int data1 = rand() % 10 + 1;
        usleep(10);
        int data2 = rand() % 10;
        char op = opers[rand() % len];
        Task t(data1, data2, op);

        // 2. 生产数据
        rq->Push(t);
        cout << "Productor task done, task is : " << t.GetTask() << " who: " << name << endl;

        sleep(1);
    }
    return nullptr;
}

void *Consumer(void *args)
{
    ThreadData *td = static_cast<ThreadData*>(args);
    RingQueue<Task> *rq = td->rq;
    std::string name = td->threadname;

    while (true)
    {
        // 1. 消费数据
        Task t;
        rq->Pop(&t);
       
        // 2. 处理数据
        t();
        cout << "Consumer get task, task is : " << t.GetTask() << " who: " << name << " result: " << t.GetResult() << endl;
        // sleep(1);

    }
    return nullptr;
}

int main()
{
    srand(time(nullptr) ^ getpid());
    RingQueue<Task> *rq = new RingQueue<Task>(50);

    pthread_t c[5], p[3];

    for (int i = 0; i < 1; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Productor-" + std::to_string(i);

        pthread_create(p + i, nullptr, Productor, td);
    }
    for (int i = 0; i < 1; i++)
    {
        ThreadData *td = new ThreadData();
        td->rq = rq;
        td->threadname = "Consumer-" + std::to_string(i);

        pthread_create(c + i, nullptr, Consumer, td);
    }

    for (int i = 0; i < 1; i++)
    {
        pthread_join(p[i], nullptr);
    }
    for (int i = 0; i < 1; i++)
    {
        pthread_join(c[i], nullptr);
    }

    return 0;
}

线程池

/*threadpool.h*/ 
/* 线程池:
* 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着 监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利 用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
* 线程池的应用场景:
* 1. 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技 术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
* 2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
* 3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情 况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限, 出现错误.
* 线程池的种类: * 线程池示例:
* 1. 创建固定数量线程池,循环从任务队列中获取任务对象,
* 2. 获取到任务对象后,执行任务对象中的任务接口
*/

Main.cc

#include <iostream>
#include <ctime>
#include "ThreadPool.hpp"
#include "Task.hpp"


pthread_spinlock_t slock;


int main()
{
    // pthread_spin_init(&slock, 0);
    // pthread_spin_destroy(&slock);

    std::cout << "process runn..." << std::endl;
    sleep(3);
    // ThreadPool<Task> *tp = new ThreadPool<Task>(5);
    ThreadPool<Task>::GetInstance()->Start();
    srand(time(nullptr) ^ getpid());

    while(true)
    {
        //构建任务
        int x = rand() % 10 + 1;
        usleep(10);
        int y = rand() % 5;
        char op = opers[rand()%opers.size()];

        Task t(x, y, op);
        ThreadPool<Task>::GetInstance()->Push(t);
        //交给线程池处理
        std::cout << "main thread make task: " << t.GetTask() << std::endl;

        sleep(1);
    }
}

ThreadPool.hpp

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 5;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
            std::cout << name << " run, "
                      << "result: " << t.GetResult() << std::endl;
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;

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

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

相关文章

elementui 实现一个固定位置的Pagination(分页)组件

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、elementui 左侧导航菜单栏与main区域联动 三、elementui 中设置图片的高度并支持PC和手机自适应 四、 elementui 实现一个固定位置的Pagination&#xff08;分页&#xff09;组件 文章目录 系列文章目录…

Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview

​ 一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用&#xff0c;主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术&#xff0c;其中包括以下几个关键步骤&#xff1a; 图像预处理&#xff1a;首先&#xff0c;对输入图像进行预处理&am…

深入剖析主机安全中的零信任机制及其实施原理

引言 在数字化转型加速与云端服务普及的大背景下&#xff0c;传统依赖边界的网络安全模式逐渐显露出其局限性。面对愈发复杂多变的威胁环境&#xff0c;零信任安全架构作为新一代的安全范式应运而生&#xff0c;尤其是在主机层面的安全实践中&#xff0c;零信任机制正扮演着至…

RabbitMQ3.x之七_RabbitMQ消息队列模型

RabbitMQ3.x之七_RabbitMQ消息队列模型 文章目录 RabbitMQ3.x之七_RabbitMQ消息队列模型1. RabbitMQ消息队列模型1. 简单队列2. Work Queues(工作队列)3. Publish/Subscribe(发布/订阅)4. Routing(路由)5. Topics(主题)6. RPC(远程过程调用)7. Publisher Confirms(发布者确认) …

防止推特Twitter账号被冻结,应该选什么代理类型IP?

在处理多个 Twitter 帐号时&#xff0c;选择合适的代理IP对于避免大规模帐户暂停至关重要。现在&#xff0c;问题出现了&#xff1a;哪种类型的代理是满足您需求的最佳选择&#xff1f;下面文章将为你具体讲解推特账号冻结原因以及重点介绍如何选择代理IP。 一、推特账号被冻结…

C# WPF编程-命令

C# WPF编程-命令 概述WPF命令模型ICommand接口RoutedCommand类RoutedUICommand类命令库 概述 使用路由事件可以响应广泛的鼠标和键盘事件&#xff0c;这些事件是低级的元素。在实际应用程序中&#xff0c;功能被划分成一些高级的任务。这些任务可通过各种不同的动作和用户界面…

如何借助API实现自动化商品采集商品,自营商城选品上货搜索无货源模式

借助API实现自动化商品采集、自营商城选品上货以及搜索无货源模式&#xff0c;通常需要以下步骤&#xff1a; 请求示例&#xff0c;API接口接入Anzexi58 确定数据源&#xff1a; 首先&#xff0c;你需要确定你的商品数据来源。这可能是其他电商平台、供应商网站、数据提供商等…

Spark 的结构化 APIs——RDD,DataFrame, Dataset, SparkSQL 使用和原理总结

文章目录 前言RDD的底层是什么?结构化 Spark主要优点和好处 DataFrame APISpark的基本数据类型Spark的结构化和复杂数据类型Schemas 和创建 DataFramesColumns 和 ExpressionsRows通用的 DataFrame 算子 The Dataset API有类型 Objects、无类型 Objects 和通用 Rows创建 Datas…

Express框架搭建项目 node.js

文章目录 引言Express框架介绍express安装环境准备写一个简单的项目展示 文章总结 引言 Express是一个基于Node.js平台的轻量级Web应用框架&#xff0c;它提供了简洁的API和丰富的功能&#xff0c;使得开发者能够快速地构建Web服务器和API。本文将带领大家从零开始&#xff0c…

【教程】MySQL数据库学习笔记(四)——数据操作语言DML(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 第四章 《数据操…

小程序滑动删除组件+全选批量删除组件+附源码

小程序滑动删除组件全选批量删除组件附源码 说明 使用 uni-app、uview 组件开发&#xff0c;全端&#xff08;微信小程序、QQ小程序、抖音小程序等等&#xff09; 支持滑动删除组件、支持左滑删除、长按进入批量删除、全选删除、长按弹窗删除、 组件式开发&#xff0c;文章…

【MATLAB源码-第176期】基于matlab的16QAM调制解调系统频偏估计及补偿算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在通信系统中&#xff0c;频率偏移是一种常见的问题&#xff0c;它会导致接收到的信号频率与发送信号的频率不完全匹配&#xff0c;进而影响通信质量。在调制技术中&#xff0c;QPSK&#xff08;Quadrature Phase Shift Keyin…

在ChatGPT中,能用DALL·E 3编辑图片啦!

4月3日&#xff0c;OpenAI开始向部分用户&#xff0c;提供在ChatGPT中的DALLE 3图片编辑功能。 DALLE 3是OpenAI在2023年9月20日发布的一款文生图模型&#xff0c;其生成的图片效果可以与Midjourney、leonardo、ideogram等顶级产品媲美&#xff0c;随后被融合到ChatGPT中增强其…

电脑上音频太多,播放速度又不一致,如何批量调节音频播放速度?

批量调节音频速度是现代音频处理中的一个重要环节&#xff0c;尤其在音乐制作、电影剪辑、有声书制作等领域&#xff0c;它能够帮助制作者快速高效地调整音频的播放速度&#xff0c;从而满足特定的制作需求。本文将详细介绍批量调节音频速度的方法、技巧和注意事项&#xff0c;…

软件杯 深度学习YOLO抽烟行为检测 - python opencv

文章目录 1 前言1 课题背景2 实现效果3 Yolov5算法3.1 简介3.2 相关技术 4 数据集处理及实验5 部分核心代码6 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习YOLO抽烟行为检测 该项目较为新颖&#xff0c;适合作为竞赛课…

CentOS 镜像下载

CentOS 镜像下载&#xff1a;https://www.centos.org/download/ 选择合适的架构&#xff0c;博主选择x86_64&#xff0c;表示CentOS7 64位系统x86架构&#xff0c;如下&#xff1a; 或者直接访问以下网站下载 清华大学开源软件镜像站&#xff1a;https://mirrors.tuna.tsin…

c语言--联合体(声明、特点、计算)

目录 一、联合体类型的声明二、 联合体的特点三、 相同成员的结构体和联合体对比四、 联合体大小的计算 一、联合体类型的声明 像结构体⼀样&#xff0c;联合体也是由⼀个或者多个成员构成&#xff0c;这些成员可以不同的类型。 但是编译器只为最大的成员分配足够的内存空间。…

韩顺平 | 零基础快速学Python

环境准备 开发工具&#xff1a;IDLE、Pycharm、Sublime Text、Eric 、文本编辑器&#xff08;记事本/editplus/notepad&#xff09; Python特点&#xff1a;既支持面向过程OOP、也支持面向对象编程&#xff1b;具有解释性&#xff0c;不需要编程二进制代码&#xff0c;可以直…

使用LangChain编写图检索查询实现RAG

大家好&#xff0c;检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;简称RAG&#xff09;是一种先进的人工智能技术&#xff0c;通过整合大型语言模型&#xff08;LLM&#xff09;的内部知识和外部权威数据源&#xff0c;来提升生成式AI模型的表现。 本文…

网站访问502,网站服务器崩溃,比较常见几个的原因

其实&#xff0c;配置再好的服务器也难免在使用过程中出现一些故障&#xff0c;造成宕机。 服务器一旦出现故障&#xff0c;影响到用户实时访问网站&#xff0c;造成用户流失&#xff0c;如果在企业的销售高峰期&#xff0c;则将直接影响到商业利润&#xff0c;而且不仅影响外…