Linux | 线程同步 | 条件变量 | 生产消费模型 | 阻塞队列实现生产消费模型

news2024/11/15 0:12:40

文章目录

    • 线程饥饿
    • 条件变量接口的使用
    • 生产者和消费者模型
      • 使用阻塞队列实现生产消费模型
      • 代码中存在的问题
      • 关于pthread_cond_wait的原子性
    • 生产消费模型中的并发体现

线程饥饿

在多线程并发执行的场景中,会不会出现这样的情况,一些线程由于优先级更高,或者调度成本较低,cpu会不断地调度这些线程,由于线程访问临界资源是互斥的,有一些线程可以频繁的访问临界资源,肯定也就有一些线程几乎没有访问临界资源,我们将这个现象称为线程饥饿,线程饥饿发生时,一些线程总是无法访问临界资源

对于临界资源来说,线程互斥可以保证线程安全,所以互斥是没有问题的,甚至是必须的。但是线程互斥一定是合理的吗?很明显,由于线程饥饿问题的存在,线程互斥不是合理的。我们希望所有的线程访问临界资源的机会是平等的,我们不能因为线程互斥而剥夺线程平等访问临界资源的权利。

所以我们需要通过规则的限制,使线程访问临界资源有序,使之合理的访问,赋予线程平等访问临界资源的权利,我们将能够使线程平等的访问临界资源的机制叫做同步机制。同步机制建立在线程互斥的前提下,我们需要先保证线程的互斥,然后使线程按照一定的顺序互斥地访问临界资源。

同步的‘同’,不是一起工作的意思,而是协同,互相配合的意思,也有协同步调的意思。每个线程协同步调,按一定的先后次序访问临界资源,就叫做同步!

条件变量接口的使用

条件变量是最经常被用来使用的实现线程同步的一个机制,当条件(由程序员设置的条件)不满足时,线程会在条件变量下休眠,当条件满足时,我们就可以唤醒在该条件变量下休眠的线程,接着该线程就能访问临界资源。也就是说,条件变量是一个为线程提供休眠的场所,并且条件变量唤醒线程是按照一定顺序的,所以使用条件变量可以实现线程的同步。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
条件变量的类型:pthread_cond_t

经常使用的条件变量接口一共有5个,其中pthread_cond_init和pthread_cond_destroy的使用和互斥锁的接口一样,这里不再赘述。

pthread_cond_wait:使线程陷入条件变量下的休眠,参数是一个条件变量和一把锁的地址,线程会陷入你传入的条件变量下的休眠,并且wait接口的使用要配合一把锁,当线程陷入休眠时,如果占用锁资源,wait会将锁释放,防止死锁的发生。

如果线程的锁被释放了,那么当线程被唤醒时,wait会为当前线程加锁,只有当前线程加了锁,wait函数才算调用完

pthread_cond_signal,pthread_cond_broadcast:两个接口都是用来唤醒在条件变量下等待的线程,需要传入条件变量的地址。他们的区别就是pthread_cond_signal会唤醒在条件变量下休眠的一个线程,而pthread_cond_broadcast会唤醒在条件变量下休眠的所有线程

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <functional>
using namespace std;

pthread_cond_t g_cond;
pthread_mutex_t g_mutex;

vector<function<void()> > functions;

void world() { cout << "hello world!" << endl;}
void linux() { cout << "hello linux!" << endl;}

void *start_routine(void *arg)
{
    while (1)
    {
        // 使线程无条件的陷入条件变量下的休眠
        pthread_cond_wait(&g_cond, &g_mutex);
        // 当线程被唤醒时,执行方法集中的所有方法
        for (auto f : functions)
        {
            f();
        }
    }
}

int main()
{
    // 方法集的加载
    functions.push_back([](){
        cout << "hello conditon!" << endl;
    });
    functions.push_back(world);
    functions.push_back(linux);
	// 条件变量的初始化
    pthread_cond_init(&g_cond, nullptr);
    pthread_mutex_init(&g_mutex, nullptr);
    // 线程的创建
    pthread_t tid1;
    pthread_t tid2;
    pthread_t tid3;
	// 线程的运行
    pthread_create(&tid1, nullptr, start_routine, (void*)"thread1");
    pthread_create(&tid2, nullptr, start_routine, (void*)"thread2");
    pthread_create(&tid3, nullptr, start_routine, (void*)"thread3");
	
	// 主线程每隔一秒唤醒一次在条件变量下休眠的线程
    while (1)
    {
        sleep(1);
        pthread_cond_signal(&g_cond);
    }
	// 线程资源的回收
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
	// 条件变量的释放
    pthread_cond_destroy(&g_cond);
	pthread_mutex_destroy(&g_mutex);
	
    return 0;
}

这段demo中的线程直接无条件的陷入条件变量下的休眠,主线程每隔一秒钟会唤醒该条件变量下的一个线程,使其执行方法集functions中的方法。
在这里插入图片描述

生产者和消费者模型

生产消费模型是最典型的同步与互斥的应用场景。当消费者要进行消费时,会进入超市购买商品,而不是直接找生产者购买。当生产者需要销售商品时,会将商品放入超市售卖,而不是直接找消费者销售。

超市这个场所对消费者和生产者进行了解耦,消费者无需关心生产者是否将商品生产出来,若没有生产商品,什么时候才能生产出商品…生产者也无需关心消费者是否需要他的商品,消费者在哪…他们只需要关心超市,生产者只关心超市是否还有剩余的货架摆放他们的商品,自己的商品是否被卖完…消费者只关心超市是否有他所需要的商品。强耦合的生产者与消费者就这样被超市降低了耦合度,超市不仅提高了双方的效率,还降低了彼此资源的无用消耗。

在这个消费的过程中,有两个角色,消费者和生产者,分别由线程承担这两种角色,可以由很多线程构成消费者或生产者,也可以由一个线程构成消费者或生产者。消费者在超市中消费,生产者通过超市销售商品,所以超市就是消费过程中的一个交易场所。

而对于生产者来说,生产者之间需要竞争超市的空间,以摆放更多的商品销售,当处于极端情况,即超市只能摆放一件商品时,一个生产者摆放了商品,其他生产者就不能摆放商品,所以生产者之间就形成了一种互斥的关系

对于消费者来说,在极端情况下,当超市的商品只有一件时,消费者之间就需要互相竞争,一个消费者购买了商品,其他消费者不能购买这个商品,所以从这个角度来说,消费者之间的关系也是互斥

对于消费者和生产者来说,在生产者将商品摆放到超市的过程中,消费者是否可以购买这个商品?在生活中,这个例子很简单,摆放商品的操作是原子性的,商品只有两种状态,被摆放到货架与未被摆放,只要商品被摆放到货架上,消费者就可以购买。但是在计算机中,摆放商品的操作可能并不是原子的,即除了未摆放和已经摆放,商品还存在中间状态,当生产者将商品摆放到一半时,消费者就不能获取该商品,否则将导致购买的商品有问题。所以生产者和消费者之间存在一种互斥关系,当生产者访问货架,摆放商品时,消费者无法访问货架,购买其摆放的商品,只有生产者将商品摆放到货架上,消费者的购买才是有效的。

当超市的货架上没有商品时,消费者进入超市无法购买商品,但是由于消费者急需这件商品,于是消费者不断的进入超市,访问该货架,但是货架上依然没有他需要的商品。

当超市的货架无法摆放更多的商品,但是生产者由于库存压力需要将商品摆放到货架上销售,于是生产者频繁的访问超市的货架,但是结果却是货架依然无法摆放更多的商品。

在这两个从场景中,无论是消费者还是生产者,他们频繁访问超市都将消耗大量的时间,但是因为条件没有就绪,他们消耗的时间都是无意义的,为了减少这样无意义的时间消耗,生产者和消费者之间应该具有某种特定的访问顺序,这个顺序就是:当商品被消费者消费,货架有了空位,生产者才可以生产商品并摆放到货架上,当生产者生产了商品并摆放到货架上,消费者才可以消费。即消费完了再生产,生产完了再消费。所以生产者和消费之间除了互斥关系,还应该具有同步关系,他们对于超市的访问需要具有一定顺序。

所以在这个模型中,两种角色产生了三种关系,它们分别是

消费者与消费者之间的互斥
生产者与生产者之间的互斥
消费者与生产者之间的互斥与同步

使用阻塞队列实现生产消费模型

将阻塞队列作为生产消费模型中的超市,生产者和消费者分别用一个线程模拟,一个线程向阻塞队列中写入数据,另一个线程从阻塞队列中读取数据。

要实现一个阻塞队列,首先肯定要有一个queue结构,并且需要设置一个变量规定queue的大小。当队列为空时,消费者就不能消费,将消费线程陷入阻塞,当队列中有数据时再唤醒消费线程。当队列满时,生产者就不能生产,将生产线程陷入阻塞,当队列有位置写入数据时再将其唤醒。生产者与消费者两者之间的等待条件不同,并且两者需要从不同的地方唤醒,所以需要有两个条件变量,分别作为两者休眠的场所,用来实现阻塞队列的有序访问

在这个模型中,无论是消费者与消费者,生产者与生产者还是生产者与消费者,他们之间都具有互斥关系,也就是说,同一时间,只允许一个线程访问阻塞队列,所以对于阻塞队列的访问要实现互斥,因此需要用到一把互斥锁

总结一下,阻塞队列中需要有一个queue结构,一个保存队列大小的变量,一把锁以及两个条件变量

template <class T>
class block_queue
{
private:
    int _cap;                // 容量
    queue<T> _bq;            // 队列
    pthread_mutex_t _mutex;  // 互斥锁
    pthread_cond_t _concond; // 给消费者等待的条件变量
    pthread_cond_t _procond; // 给生产者等待的条件变量
};

然后是阻塞队列的构造与析构,分别用来初始化队列的容量,锁,条件变量,以及析构锁,条件变量

block_queue(uint32_t cap = 10) :_cap(cap)
{
    pthread_mutex_init(&_mutex, nullptr);
    pthread_cond_init(&_concond, nullptr);
    pthread_cond_init(&_procond, nullptr);
}
~block_queue()
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_concond);
    pthread_cond_destroy(&_procond);
}

接着是生产与消费的逻辑,先用伪代码编写

// 生产者生产数据
void push(const T& in)
{
    // 上锁
    // 条件判断,如果队列满了,生产线程陷入阻塞
    // 队列没满,生产数据
    // 解锁
    // 因为数据已经写入了队列,所以唤醒消费线程,使之消费数据

    lock();
    if (is_full())
    {
        // 生产者陷入等待
        pro_wait();
    }
    // 满足队列不满的条件
    push(in);
    unlock();
    wakeup_con();
}
// 消费者消费数据
T pop()
{
    // 上锁
    // 条件判断,如果队列为空,消费线程陷入阻塞
    // 如果队列不为空,消费数据
    // 解锁
    // 此时的队列肯定不为空,所以唤醒生产线程,使之生产数据
    lock();
    if (is_empty())
    {
        // 消费者陷入等待
        con_wait();
    }
    // 满足队列有数据的情况
    T ret = pop();
    unlock();
    wakeup_pro();

    return T;
}

伪代码中,所有的接口都进行了封装,接下来就需要一个个封装这些接口

#include <iostream>
#include <pthread.h>
#include <queue>
using namespace std;
template <class T>
class block_queue
{
public:
    block_queue(uint32_t cap = 10) :_cap(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_concond, nullptr);
        pthread_cond_init(&_procond, nullptr);
    }
    ~block_queue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_concond);
        pthread_cond_destroy(&_procond);
    }
    // 生产者生产数据
    void push(const T& in)
    {
        // 上锁
        // 条件判断,如果队列满了,生产线程陷入阻塞
        // 队列没满,生产数据
        // 解锁
        // 因为数据已经写入了队列,可以唤醒消费线程,消费数据

        lock();
        if (is_full())
        {
            // 生产者陷入等待
            pro_wait();
        }
        // 满足队列不满的条件
        push_data(in);
        unlock();
        wakeup_con();
    }
    // 消费者消费数据
    T pop()
    {
        // 上锁
        // 条件判断,如果队列为空,消费线程陷入阻塞
        // 如果队列不为空,消费数据
        // 解锁
        // 此时的队列肯定不为空,可以唤醒生产线程,生产数据
        lock();
        if (is_empty())
        {
            // 消费者陷入等待
            con_wait();
        }
        // 满足队列有数据的情况
        T ret = pop_data();
        unlock();
        wakeup_pro();

        return ret;
    }
private:
	// 上锁与解锁
    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }
	// 判断队列是否为空
    bool is_full()
    {
        return _bq.size() == _cap; // 返回当前队列的大小是否等于队列容量
    }
    bool is_empty()
    {
        return _bq.empty();
    }
    // 数据的生产与消费
    // 就是入队和出队接口的封装
    void push_data(const T& in)
    {
        _bq.push(in);
    }
    // 出队时返回队头元素
    T pop_data()
    {
        T ret = _bq.front();
        _bq.pop();
        return ret;
    }
	// 生产者与消费者的阻塞等待
    void pro_wait()
    {
        pthread_cond_wait(&_procond, &_mutex);
    }
    void con_wait()
    {
        pthread_cond_wait(&_concond, &_mutex);
    }
	// 唤醒生产者与消费者
    void wakeup_con()
    {
        pthread_cond_signal(&_concond);
    }
    void wakeup_pro()
    {
        pthread_cond_signal(&_procond);
    }

    int _cap;                // 容量
    queue<T> _bq;            // 队列
    pthread_mutex_t _mutex;  // 互斥锁
    pthread_cond_t _concond; // 给消费者等待的条件变量
    pthread_cond_t _procond; // 给生产者等待的条件变量
};
void* consumer(void* arg)
{
    block_queue<int>* bqp = static_cast<block_queue<int>*>(arg);
    while (1)
    {
        int ret = bqp->pop();
        cout << "线程[" << pthread_self() << "]" << "消费了一个数据" << ret << endl;
    }
}


void* producer(void* arg)
{
    block_queue<int>* bqp = static_cast<block_queue<int>*>(arg);
    while (1)
    {
        
        sleep(1);
        int data = rand() % 10;
        bqp->push(data);
        cout << "线程[" << pthread_self() << "]" << "生产了一个数据" << data << endl;
    }
}

int main()
{
    srand((unsigned long)time(nullptr));
    block_queue<int> bq;
    pthread_t tid1, tid2;
    pthread_create(&tid1, nullptr, consumer, &bq);
    pthread_create(&tid2, nullptr, producer, &bq);

    while (1)
    {
        sleep(1);
    }
    return 0;
}

这段demo的生产线程先休眠一秒,然后每隔1秒生产一个数据,消费者线程会不断的陷入条件变量下的休眠,当生产者线程生产了数据,它才会被唤醒,然后读取数据
在这里插入图片描述

代码中存在的问题

在这里插入图片描述
当队列满了后,生产线程会陷入等待,而如果你的代码逻辑比较复杂,或者其他什么原因,导致了操作系统错误的唤醒了在条件变量下等待的生产线程,该线程向后执行,将往满了的队列中生产数据,这可能会导致程序的运行错误

所以由于伪唤醒的存在,我们不仅需要唤醒线程,还需要再次对队列进行判断。使用if不能实现这样的操作,而使用while就能实现这样的操作,线程被唤醒后,会回到前面,判断while的条件是否成立,这就是一次条件的再判断,当条件不成立(线程可以访问临界资源了),while退出,线程执行后面的代码。所以这里用while代替if更合适

在这里插入图片描述
在解锁前还是解锁后唤醒线程呢,两者有差别吗?

以生产者生产为例,如果唤醒消费者线程的操作在解锁之前,那么生产者向队列生产完数据,就会唤醒正在等待的消费者线程,虽然唤醒消费者线程的条件满足了(队列中有数据),但是唤醒消费者线程还需要对其加锁(因为此时的消费者线程位于临界区,pthread_cond_wait会为线程自动加锁),由于生产者线程没有解锁,消费者线程加锁失败(此时的线程还卡在pthread_cond_wait函数中),消费者线程的等待就从等待条件的就绪变为了等待占有锁的线程解锁。当生产者线程解锁,消费者线程或者其他没有在等待的消费者线程就可以拿到这把锁,进入阻塞队列消费。这个过程不会产生什么错误

还是这个情况,如果其他消费者线程先于被唤醒的消费者线程拿到所,进入了队列消费,也不会导致程序崩溃,因为当被唤醒的线程拿到锁后会再次进行对队列的条件进行判断(while的作用),如果队列的条件不允许消费,被唤醒的线程会再次陷入条件的等待。

如果生产者线程在解锁之后才唤醒消费者线程,那么在消费者线程被唤醒之前,可能生产者线程被切换,cpu运行其他没有在等待的消费者线程,这些消费者线程看到队列中有数据,并且可以申请到锁进入队列,所以它们就进入队列消费数据。当生产者线程被切回重新执行,接着唤醒消费者线程,该消费者线程在拿到锁后,由于while的条件判断,会再次判断队列中的条件是否适合进行消费,如果条件不适合,就继续陷入等待,如果条件适合就进入阻塞队列消费。所以这个过程也不会出错

分析完两种情况,可以看出无论唤醒线程的操作是在解锁前还是在解锁后,对于最后的结果都是没有影响的并不会出错的。

关于pthread_cond_wait的原子性

执行pthread_cond_wait时,线程如果持有锁,pthread_cond_wait会解锁(不能让线程带着锁陷入等待),再陷入等待,解锁和等待必须是一个原子操作,也就是说,pthread_cond_wait要么被执行完,要么没有被执行,如果它不是一个原子操作,假如线程A被解锁之后,陷入等待之前,有其他线程申请到了这把锁,然后修改了阻塞队列,此时队列的条件满足线程A的唤醒条件,线程A会被唤醒,但是由于线程A只是解锁了,还没有陷入等待,所以唤醒线程A失败。

接着cpu会继续运行线程A,线程A陷入了等待,但是有没有可能该队列永远不会出现让线程A唤醒的条件呢?如果是这样,线程A会陷入永久的等待,资源也就泄漏了,所以pthread_cond_wait的解锁必须是原子操作

生产消费模型中的并发体现

上面我实现的阻塞队列有体现线程的并发吗?生产者线程进入队列时,其他线程包括消费者线程都不能进入队列,因为要保证它们之间的互斥,所以线程在访问队列时,总是串行式的访问,必须等待其他线程访问完队列,当前线程才能进入队列。可能有人就会问了,这样访问临界资源的效率不是很低吗?这个模型中的并发体现在哪?

虽然互斥的访问队列效率比较低,但是访问队列的时间相对其他时间是比较少的,也就是访问队列的速度很快,线程访问队列的操作只有取出数据与写入数据。在线程写入数据前与取出数据后,线程要生产数据和处理数据,在这个模型中,重要的并不是取出与写入数据,阻塞队列只是一个通信的桥梁,这个模型中最重要的是数据的生产与处理,因为它们消耗的时间很显然比取出与写入数据所消耗的时间多。

所以生产消费模型中的并发体现在:生产数据与处理数据的并发,队列只是线程间通信的桥梁,线程通信前与线程通信后的线程是并发运行的。

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

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

相关文章

分布式锁4-Redisson分布式锁实现与看门狗原理

文章目录一.Redisson介绍二.分布式锁的运用1.引入依赖.2.增加配置类.3.简单代码实现1.不指定加锁时间,会默认启动看门狗.自动帮你的锁进行续期.2.指定加锁时间,不启用看门狗续期,到期自动解锁.三.分布式锁实现原理加锁过程看门狗续期过程一.Redisson介绍 Redisson 是架设在 Red…

读《冯诺依曼传》

关于冯诺依曼 冯诺依曼是20世纪的全才,原名约翰尼,匈牙利美籍科学家,被称为计算机之父和博弈论之父。计算机和博弈论都深刻改变人类的生活工作和思维方式,极大地促进了社会和人类文明的进步发展。在六个哲学领域他都做出了相当大的贡献,把模糊的问题用数学精确地表述出来…

扩散模型的迁移和应用(DiffusionDet,AR-LDM,MagicVideo,RenderDiffusion,AltDiffusion,VD)

在上一篇博文中&#xff0c;博主已经整理了扩散模型&#xff08;Diffusion Model&#xff0c;DDPM&#xff0c;GLIDE&#xff0c;DALLE2&#xff0c;Stable Diffusion&#xff09;的基本原理&#xff0c;后续不再赘述其细节。作为一个最近被讨论热烈的方向&#xff0c;很自然地…

干Java开发快30岁了,没有核心竞争力,该怎么跳槽面试?

今年互联网大环境的确不怎么好&#xff0c;互联网公司纷纷陷入裁员风波并缩减规模&#xff0c;这就导致更多程序员需要去竞争更少的就业岗位&#xff0c;搞的整个IT 行业越来越卷。作为Java程序员的我们就更不用说了&#xff0c;除去加班的可能性&#xff0c;每天上班8小时需要…

Java项目:SSM药品进货销售仓储信息管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能&#xff1a; 管理员登录,用户信息管理,个人信息管理,药品信息管理,药品类别信息管理,选购药品管理,订单管理,订单记录管…

余生很短,及时止损--爱摸鱼的美工(九)

-----------作者&#xff1a;天涯小Y 余生很短&#xff0c;及时止损 今日份垃圾清理完毕 有的人就像垃圾车 他们装满了垃圾四处奔走 充满懊悔、愤怒.失望的情绪 随着垃圾越堆越高 就需要找地方倾倒&#xff0c;释放出来 他们会逮着一切机会 到处碰瓷、找茬、泄愤 如果你给他们…

Js逆向教程22-AST 抽象语法树babel安装

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; AST 抽象语法树babel安装 一、什么是AST js代码&#xff1a; var a1;json标识&#xff1a; 赋值语句左边为定义定义的名称为a(作用域…

python——面向对象

1、概念 ①OOP&#xff1a;Object Oriented Programming ②面向对象&#xff1a;只关心哪个对象完成这个功能&#xff0c;它关注的是完成功能的结果&#xff0c;以结果为导向 python、Java、js、c... ③面向过程&面向对象 ④面向对象三大特征&#xff1a;封装、继承、多…

springcloud(配置中心)

目录 1. 为何需要配置中心2. nacos的配置中心 2.1 配置示例2.2 多环境配置2.3 GROUP2.4 namespace 1. 为何需要配置中心 没有配置中心的情况下会出现什么情况&#xff1a; 如果没有配置中心&#xff0c;则各个服务的配置文件分散在各个项目中&#xff0c;不方便维护出于安全考…

代码审计学习 : xhcms

前言 从大佬那里听说&#xff0c;xhcms 很适合代码审计的新手。 环境 php 5.4.45 Apache 2.4.39 Mysql 5.7.26 文件上传配合文件包含 /index.php 和 /admin/index.php error_reporting(0); //关闭错误显示 $fileaddslashes($_GET[r]); //接收文件名 $action$file?index:…

FH30502输入3.7V升5V电流3A-5A同步整流升压芯片

3.3V升5V电流3A-5A同步整流升压芯片&#xff0c;2.7V到18V的输入电压支持供电系统和电池的较宽范围应用。FH30502根据负载情况的变化自动切换工作模式&#xff0c;在轻载Burst模式下静态电流处于低状态。FH30502使用自适应常数断开时间峰值电流模式控制。FH30502有一个内部特性…

C 语言实现经典贪吃蛇游戏

原文链接&#xff1a;C语言 贪吃蛇游戏 文章目录一、说明二、效果2.1 欢迎界面2.2 游戏规则2.3 得分排行2.4 退出游戏2.5 游戏界面2.6 游戏结束三、源码3.1 cmd.h3.2 cmd.c3.3 io.h3.4 io.c3.5 model.h3.6 service.h3.7 service.c3.8 ui.h3.9 ui.c3.10 utils.h3.11 utils.c3.12…

m蜂窝移动通信系统中越区切换的matlab仿真分析

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 用Matlab仿真一个蜂窝模型&#xff0c;用户在打电话时产生切换的过程。建立蜂窝越区切换&#xff0c;假设有16个蜂窝&#xff0c;实现两个小区之间的硬切换&#xff0c;每个小区假设能容纳30个用…

UDS入门至精通系列:Service 14

文章目录 一、Service 14功能是什么二、UDS协议对服务的定义三、用图形说明Service 14四、手动测试总结一、Service 14功能是什么? 在汽车电子诊断领域,在新车型定义诊断需求时,会给每一个ECU的故障类型定义一个DTC,ECU中运行代码判定DTC是否产生(判定机制和原理我会在关于…

Gateway之限流、熔断

目录 一、Sentinel--服务容错 ① 简化启动我们的nacos 1.高并发带来的问题 ① 修改配置文件中tomcat的并发数 ② 使用压测工具&#xff0c;对请求进行压力测试 第一步&#xff1a;修改配置&#xff0c;并启动软件 第二步&#xff1a;添加线程组 第三步&#xff1a;配置线…

MySQL 小版本升级步骤

MySQL mysql 5.7.38 升级到 5.7.40 下载软件升级包 参考下载地址&#xff1a;https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.40-linux-glibc2.12-x86_64.tar.gz 上传软件包至服务器 可以使用rz命令或scp上传本地文件 比如上传至服务器目录为 /usr/local/software…

关于如何获取上周均值的实时数据思路,吾之解合君意否?

&#x1f4da; 在前端界面图形化展示中&#xff0c;目前主流以报表形式&#xff0c;或者以曲线图形式展示其所需数据的数字动态变化效果居多。在数据量不大或者不需要模糊对比的情况下&#xff0c;我们以报表展示为主&#xff1b;而我们需要从肉眼宏观可见的监控数据变化&#…

volatile解决有序性和可见性问题

线程可见性问题分析 什么是可见性&#xff1f; 如果一个线程对一个共享变量进行了修改 而其他线程不能及时地读取修改后的值 所以在多线程情况下该共享变量就会存在可见性问题 package com.alipay.alibabademo.thread;import lombok.extern.slf4j.Slf4j;import java.util.co…

改进遗传算法在TSP问题中的应用(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

玩转Python图片处理(OpenCV)

OpenCV是一个基于BSD许可&#xff08;开源&#xff09;发行的跨平台计算机视觉库&#xff0c;可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C 类构成&#xff0c;同时提供了Python、Ruby、MATLAB等语言的接口&#xff0c;…