【Linux】生产者消费者模型

文章目录

  • 一、生产者消费者模型
    • 1. 生产者消费者模型的概念
    • 2. 生产者消费者之间的关系
    • 3. 生产者和消费者的特点
  • 二、基于BlockingQueue的生产者消费者模型
    • 1. 单生产单消费
      • 随机数任务
      • 计算器任务Task
    • 2. 多生产多消费
    • 3. 为什么生产者消费者模型高效
  • 三、基于环形队列的生产消费模型
    • 1. POSIX信号量
    • 2. 基于环形队列的生产消费模型
      • 单生产单消费
      • 多生产多消费
    • 3. 信号量的意义


一、生产者消费者模型

1. 生产者消费者模型的概念

生产者-消费者模型 是一种常见的多线程编程模式,用于解决生产者和消费者之间协作的问题。在该模型中,生产者负责生产数据,并将数据放入共享的缓冲区;消费者则从缓冲区中取出数据并进行消费。

下面我们来看一个现实生活中的例子:消费者——超市——供货商的例子来理解一下生产者消费者模型。

在这里插入图片描述

现实生活中消费者要求比较零散,供货商生产能力很强,但是考虑到成本问题,所以就需要超市这种零售行业,超市的存在使得生产者和消费者的效率变得更高。同时,它的存在也允许了生产者和消费者的步调不一致。

在计算机中,生产者和消费者代表线程,超市可以看作是 特定的缓冲区,生产者把自己的数据交给特定的缓冲区,再由消费者把数据取走,这种工作模式即生产者——消费者模型

注意:

  • 交易场所(缓冲区)一定是会被多线程并发访问的公共区域
  • 多线程一定要保护共享资源的安全,要维护线程同步与互斥的关系

2. 生产者消费者之间的关系

💕 生产者和消费者之间的三种关系:

  • 生产者和生产者之间为互斥关系
  • 消费者和消费者之间为互斥关系
  • 生产者和消费者之间为互斥与同步关系(互斥:保证读写安全,同步:当缓冲区数据满了或者空了,能够互相等待和通知对方)

💕 两种角色: 生产者线程和消费者线程

💕 一个交易场所: 一段特定结构的缓冲区


3. 生产者和消费者的特点

  1. 生产线程和消费线程进行解耦
  2. 支持生产和消费在一段时间忙闲不均
  3. 支持并发、提高效率。例如:生产者线程在缓冲区生产函数时参数的时候,消费者线程也可以正常运行。这两个线程由原来的串行执行变为并发执行,提高效率。

二、基于BlockingQueue的生产者消费者模型

在多线程编程中 阻塞队列(Blocking Queue) 是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述

💕 阻塞队列(blockQueue.hpp)的实现:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
using namespace std;const int gcap = 5;template <class T>
class BlockQueue
{
public:BlockQueue(const int cap = gcap):_cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_consumerCond, nullptr);pthread_cond_init(&_productorCond, nullptr);}bool isFull(){ return _q.size() == _cap; }bool isEmpty(){ return _q.empty(); }void push(const T& in){pthread_mutex_lock(&_mutex); // 我们只能在临界区内部,判断临界资源是否就绪!注定了我们在当前一定是持有锁的!while(isFull()){pthread_cond_wait(&_productorCond, &_mutex);}_q.push(in);pthread_cond_signal(&_consumerCond);pthread_mutex_unlock(&_mutex);}void pop(T* out){pthread_mutex_lock(&_mutex);while(isEmpty()){pthread_cond_wait(&_consumerCond, &_mutex);}*out = _q.front();_q.pop();pthread_cond_signal(&_productorCond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_consumerCond);pthread_cond_destroy(&_productorCond);}private:queue<T> _q;int _cap;pthread_mutex_t _mutex;pthread_cond_t _consumerCond;pthread_cond_t _productorCond;
};

将阻塞队列看作一个整体,共享资源都存在队列中,为了防止多线程并发访问共享资源存在问题,我们将互斥锁引入阻塞队列类内部。

同时,为了让生产者线程和消费者线程访问临界资源阻塞队列时能够按照一定的顺序,若队列中没有数据存在,则不让消费者消费,若队列中数据满了,不让生产者进行生产数据。所以需要引入条件变量

同时,为了保证生产者和消费者互相等待,当生产者生产数据后,就可以唤醒正在等待的消费者线程,当消费者消费数据后,可以唤醒正在等待的生产者线程。我们需要引入两个条件变量:consumercond 和 productorcond


注意:

在这里插入图片描述

假设单生产多消费模型,当消费者pop数据后队列节省出一个空间,但是却使用了pthread_cond_broadcast函数唤醒了多个生产者线程,导致了多个生产者线程 生产出了多个数据,那么队列的容量将会不够。


1. 单生产单消费

随机数任务

💕 main.cc

#include <pthread.h>
#include <unistd.h>
#include <ctime>#include "task.hpp"
#include "blockQueue.hpp"void* consumer(void* args)
{BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);while(true){// sleep(1);int data = 0;// 将数据从阻塞队列中取出bq->pop(&data);// 结合某种业务逻辑, 处理数据cout << "consumer data: " << data << endl;}
}void* productor(void* args)
{BlockQueue<int>* bq = static_cast<BlockQueue<int>*>(args);while(true){sleep(1);// 随机数获取函数获取随机数并指定其范围int data = rand() % 10 + 1;// 完成生产任务——将数据推送到阻塞队列中        bq->push(data);cout << "productor data: " << data << endl;}
}int main()
{srand((uint64_t)time(nullptr) ^ getpid());BlockQueue<int>* bq = new BlockQueue<int>();pthread_t p, c;pthread_create(&p, nullptr, productor, bq);pthread_create(&c, nullptr, consumer, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0;
}

生产者生产快消费者消费慢:

在这里插入图片描述
在这里插入图片描述

可以看到生产者先生产一大批数据,稳定后,消费者先消费一个,生产者再生产一个。

生产者生产慢消费者消费快:

在这里插入图片描述
在这里插入图片描述

此时就是消费者再等生产者了,生产一个消费一个,而且消费的都是最新生产出来的数据。


计算器任务Task

💕 task.hpp

#pragma once
#include <iostream>
#include <string>
using namespace std;class Task
{
public:Task(){}Task(int x, int y, char op):_x(x), _y(y), _op(op), _result(0), _exitcode(0){}void operator()(){switch (_op){case '+':_result = _x + _y; break;case '-':_result = _x - _y;break;case '*':_result = _x * _y;break;case '/':{if(_y == 0)_exitcode = -1;else _result = _x / _y;}break;case '%':{if(_y == 0)_exitcode = -1;else _result = _x % _y;}break;default:break;}}string formatArge(){return to_string(_x) + _op + to_string(_y) + "=";}string formatRes(){return to_string(_result) + "(" + to_string(_exitcode) + ")";}~Task(){}private:int _x;int _y;char _op;int _result;int _exitcode;
};

💕 main.cc

#include <pthread.h>
#include <unistd.h>
#include <ctime>#include "task.hpp"
#include "blockQueue.hpp"void* consumer(void* args)
{BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(args);while(true){Task t;bq->pop(&t);t();cout << pthread_self() << " | consumer data: " << t.formatArge() << t.formatRes() << endl;}
}void* productor(void* args)
{BlockQueue<Task>* bq = static_cast<BlockQueue<Task>*>(args);string opers = "+-*/%";while(true){sleep(1);// 随机数获取函数获取随机数并指定其范围int x = rand() % 20 + 1;int y = rand() % 10 + 1;char op = opers[rand() % opers.size()];Task t(x, y, op);bq->push(t);cout << pthread_self() << " | productor data: " << t.formatArge() << "?" << endl;}
}int main()
{// 单生产和单消费srand((uint64_t)time(nullptr) ^ getpid());BlockQueue<Task>* bq = new BlockQueue<Task>();pthread_t p, c;pthread_create(&p, nullptr, productor, bq);pthread_create(&c, nullptr, consumer, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0;
}

在这里插入图片描述


2. 多生产多消费

💕 main.cc

int main()
{srand((uint64_t)time(nullptr) ^ getpid());BlockQueue<Task>* bq = new BlockQueue<Task>();// 多生产和多消费pthread_t p[2], c[3];pthread_create(&p[0], nullptr, productor, bq);pthread_create(&p[1], nullptr, productor, bq);pthread_create(&c[0], nullptr, consumer, bq);pthread_create(&c[1], nullptr, consumer, bq);pthread_create(&c[2], nullptr, consumer, bq);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(c[2], nullptr);delete bq;return 0;
}

在这里插入图片描述


3. 为什么生产者消费者模型高效

从上面多生产多消费的代码我们可以看到,大量的生产者消费者线程都在竞争同一把锁?也就是一次只能放一把锁去阻塞队列里,那么这种效率岂不是非常慢吗?

传统的线程运作方式是让大部分线程阻塞在临界区之外,而生产者消费者模型是将任务工序分开,一组线程分为生产者,另一组分为消费者。充分利用了生产者的阻塞时间,用以提前准备好生产资源;同时也利用了消费者计算耗时的问题,让消费者线程将更多的时间花在计算上,而不是抢不到锁造成线程“干等”。

生产者消费者模型可以在生产前和消费后的线程并行执行,减少线程阻塞时间。


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

1. POSIX信号量

POSIX信号量SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。信号量本质是一个计数器,用于描述临界资源的数目。

  • 二元信号量: 计数器维护的value只有0和1两种可能,1表示可以访问资源,0表示不可以访问资源,以此来实现互斥,所以也称为互斥信号量(互斥锁)。

每一个线程,在访问对应的资源的时候,现申请信号量,申请成功,表明该线程允许使用该资源,申请不成功,目前无法使用该资源!

信号量的工作机制:类似于我们看电影买票,是一种资源的预定机制。

信号量已经是资源的计数器了,申请信号量成功,本身就表明资源可用,申请信号量失败本身表明资源不可用——本质就是把判断转化成信号量的申请行为

💕 初始化信号量

在这里插入图片描述

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

💕 销毁信号量

> int sem_destroy(sem_t *sem);

int sem_destroy(sem_t *sem);

💕 等待信号量

在这里插入图片描述

int sem_wait(sem_t *sem); 
  • P操作,等待信号量,会将信号量的值-1

💕 发布信号量

在这里插入图片描述

int sem_post(sem_t *sem);
  • V操作,发布信号量,表示资源使用完毕,可以归还资源了,并将信号量的值加1

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

基于环形队列的生产消费模型 是一种常见的并发编程模型,用于解决生产者和消费者之间的协作问题。在该模型中,生产者将数据放入一个共享的环形队列中,而消费者则从队列中取出数据进行处理。

在这里插入图片描述

💕 构建CP问题

  • 生产者向tailpush数据,消费者向headpop数据
  • 生产者和消费者关心的资源不一样,生产者关心的是空间,消费者关心的是数据
  • 环形队列只要访问的是不同的区域,生产和消费的行为可以同时进行。
  • 生产者和消费者访问同一个区域时怎么处理?
    • 当队列中没有资源的时候,也就是队列为空时,此时应该让生产者先运行。
    • 当队列为满的时候,生产者和消费者指向同一个位置时,此时应该让消费者先运行。

💕 申请和释放资源

生产者申请空间资源,释放数据资源

对于生产者来说,生产者每次申请数据前都需要申请space_sem,如果space_sem不为0,则申请信号量成功,否则申请信号量失败,生产者则需要在space_sem的等待队列中进行阻塞等待,直到环形队列中有新的空间资源,才能被唤醒。

当生产者生产数据后,应该释放data_sem,这里我们需要注意的是,生产者生产前是对space_sem进行P操作,但是生产结束后并不是对space_sem进行V操作,而是对data_sem进行V操作。

消费者申请数据资源,释放空间资源

对于消费者来说,消费者每次消费数据前都需要申请data_sem,如果data_sem不为0,则申请信号量成功,如果data_sem为0,则申请信号量失败,消费者需要在data_sem的等待队列中进行阻塞等待,直到环形队列中有新的数据资源,才能被唤醒。

当消费者消费完数据后,需要释放data_sem,同时消费结束后对space_sem进行V操作。

💕 两个规则

规则一:生产者和消费者不能对同一个位置进行访问。

如果生产者和消费者访问的是环形队列中的同一个位置,那么就相当于生产者和消费者同时对一块临界资源进行访问,这样就会导致数据不一致的问题。当然,如果访问的是不同的位置,那么生产者和消费者就可以并发的进行生产和消费数据。

通过信号量九三保证了当生产者和消费者指向环形队列中的同一个位置时,生产和消费的串行化过程,同时也保证了当生产者和消费者执行的不是同一个位置时,生产者和消费者可以并发的进行生产和消费,以提高效率。

规则二:生产者不能将消费者套圈,消费者不能超过生产者。

  • 如果生产者将消费者套圈了,那么就会出现这样的情况:消费者还没有将生产者之前生产的数据消费掉,该数据就被覆盖掉了,这很显然是不允许的。所以当生产者生产了一圈后,再次遇到消费者时,生产者就不能再进行生产了,需要等消费者消费数据后,才能进行生产。
  • 如果消费者超过了生产者,那么就会出现这样的情况:消费者会将之前已经消费过的废弃数据再消费一次,这也是不允许的。所以当消费者消费一圈后,再次遇到生产者,消费者就不能再进行消费了,需要等生产者生产数据后,才能进行消费。

单生产单消费

随机数任务

💕 RingQueue.hpp

#include <iostream>
#include <vector>
#include <semaphore.h>
using namespace std;const int N = 5;template <class T>
class RingQueue
{
private:void P(sem_t &s){sem_wait(&s);}void V(sem_t &s){sem_post(&s);}
public:RingQueue(int cap = N):_cap(cap), _ring(cap){// 初始化信号量sem_init(&_space_sem, 0, _cap);sem_init(&_data_sem, 0, 0);_p_step = _c_step = 0;}// 生产void push(const T& in){P(_space_sem); // 申请信号量_ring[_p_step++] = in;_p_step %= _cap;V(_data_sem); // 释放信号量}// 消费void pop(T* out){P(_data_sem);*out = _ring[_c_step++];_c_step %= _cap;V(_space_sem);}~RingQueue(){// 销毁信号量sem_destroy(&_data_sem);sem_destroy(&_space_sem);}private:vector<T> _ring;int _cap; // 环形队列容量sem_t _data_sem; // 数据信号量——只有消费者关心sem_t _space_sem; // 空间信号量——只有生产者关心int _p_step; // 生产者下标int _c_step; // 消费者下标
};

💕 Main.cc

#include "RingQueue.hpp"
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <string>const string ops = "+-*/%"; // 单生产单消费void* consumerRoutine(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true){int data = 0;rq->pop(&data);cout << "consumer done: " << data << endl;}
}void* productorRoutine(void* args)
{RingQueue<int>* rq = static_cast<RingQueue<int>*>(args);while(true){sleep(1);int data = rand() % 10 + 1;rq->push(data);cout << "productor done: " << data << endl;}
}int main()
{srand(time(nullptr)^getpid());RingQueue<int> *rq = new RingQueue<int>();// 单生产单消费pthread_t p, c;pthread_create(&p, nullptr, consumerRoutine, rq);pthread_create(&c, nullptr, productorRoutine, rq);pthread_join(p, nullptr);pthread_join(c, nullptr);return 0;
}

在这里插入图片描述

计算器任务

💕 Main.cc

#include "RingQueue.hpp"
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <string>
void* consumerRoutine(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);while(true){Task t;rq->pop(&t);t();cout << "consumer done, 处理完成的任务是: " << t.formatArge() << t.formatRes() << endl;}
}void* productorRoutine(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);while(true){sleep(1);int x = rand() % 100;int y = rand() % 100;char op = ops[(x + y) % ops.size()];Task t(x, y, op);rq->push(t);cout << "productor done, 生产的任务是: " << t.formatArge() << "?" << endl;}
}int main()
{srand(time(nullptr) ^ getpid());RingQueue<Task>* rq = new RingQueue<Task>();pthread_t c, p;pthread_create(&c, nullptr, consumerRoutine, rq);pthread_create(&p, nullptr, productorRoutine, rq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete rq;return 0;
}

在这里插入图片描述


多生产多消费

对于多生产多消费的这种情况,因为生产者和消费者不止是一个,所以就会存在生产者和生产者之间的竞争关系、消费者和消费者之间的竞争关系。因此,在这种情况下,我们就需要对临界资源进行加锁保护。

_p_pos 和 c_pos 的更新需要再加锁和解锁之间。如果它们的更新不在加锁和解锁之间,将可能会出现这样的情况:线程 A 释放了锁并没来得及将下标进行更新,然后线程 B 就获得了锁并执行到更新下标的地方,这样就有可能会出现数据不一致的问题!

💕 RingQueue.hpp

#include <iostream>
#include <vector>
#include <semaphore.h>
using namespace std;const int N = 5;template <class T>
class RingQueue
{
private:void P(sem_t &s){sem_wait(&s);}void V(sem_t &s){sem_post(&s);}void Lock(pthread_mutex_t& m){pthread_mutex_lock(&m);}void Unlock(pthread_mutex_t& m){pthread_mutex_unlock(&m);}
public:RingQueue(int cap = N):_cap(cap), _ring(cap){// 初始化信号量sem_init(&_space_sem, 0, _cap);sem_init(&_data_sem, 0, 0);_p_step = _c_step = 0;pthread_mutex_init(&_c_mutex, nullptr);pthread_mutex_init(&_p_mutex, nullptr);}// 生产void push(const T& in){P(_space_sem); // 申请信号量Lock(_p_mutex);_ring[_p_step++] = in;_p_step %= _cap;Unlock(_p_mutex);V(_data_sem); // 释放信号量}// 消费void pop(T* out){P(_data_sem);Lock(_c_mutex);*out = _ring[_c_step++];_c_step %= _cap;Unlock(_c_mutex);V(_space_sem);}~RingQueue(){// 销毁信号量sem_destroy(&_data_sem);sem_destroy(&_space_sem);pthread_mutex_destroy(&_c_mutex);pthread_mutex_destroy(&_p_mutex);}private:vector<T> _ring;int _cap; // 环形队列容量sem_t _data_sem; // 数据信号量——只有消费者关心sem_t _space_sem; // 空间信号量——只有生产者关心int _p_step; // 生产者下标int _c_step; // 消费者下标pthread_mutex_t _c_mutex;pthread_mutex_t _p_mutex;
};

这里比较推荐先申请信号量,如果先申请锁,意味着生产线程只要持有锁了,其他线程再也没有机会进入后续的代码逻辑了,意味着其他线程在当前线程持有锁的情况下,不能够进行信号量的申请,不能够先分配好资源。效率会比较低一些。

如果先申请信号量,就能保证生产线程在持有锁期间,其他线程也可以进行信号量的申请。也就是资源的分配,因此可以大大提高效率。

💕 Main.cc

#include "RingQueue.hpp"
#include "Task.hpp"
#include <ctime>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <string>const string ops = "+-*/%"; 
void* consumerRoutine(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);while(true){Task t;rq->pop(&t);t();cout << "consumer done, 处理完成的任务是: " << t.formatArge() << t.formatRes() << endl;}
}void* productorRoutine(void* args)
{RingQueue<Task>* rq = static_cast<RingQueue<Task>*>(args);while(true){sleep(1);int x = rand() % 100;int y = rand() % 100;char op = ops[(x + y) % ops.size()];Task t(x, y, op);rq->push(t);cout << "productor done, 生产的任务是: " << t.formatArge() << "?" << endl;}
}int main()
{srand(time(nullptr) ^ getpid());RingQueue<Task>* rq = new RingQueue<Task>();pthread_t c[3], p[2];for(int i = 0; i < 3; i++)pthread_create(c + i, nullptr, consumerRoutine, rq);for(int i = 0; i < 2; i++)pthread_create(p + i, nullptr, productorRoutine, 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);delete rq;return 0;
}

在这里插入图片描述

多生产多消费的意义:

生产的本质是将私有的任务或数据放入到公共空间中,消费的本质是将公共空间中的任务或数据变成私有。生产和消费并不是简单地将任务或数据放入到交易场所或从交易场所中取出,还需要考虑数据或任务放入到交易场所前和拿到任务或数据之后的处理,这两个过程是最耗费时间的。所以,多生产多消费的意义就在于能够并发地生产和处理任务。


3. 信号量的意义

信号量本质是一个计数器,那这个计数器的意义是什么呢?计数器的意义就是不用进入临界区,就可以得知临界资源的情况,甚至可以减少临界区内部的判断!而在基于阻塞队列的生产者和消费者模型中,需要申请锁,然后进行临界资源是否满足的判断再进行访问,最后释放锁,需要进行判断的原因就是我们并不清楚临界资源的情况!而信号量要提前预设资源的情况,在进行 PV 操作的过程中,我们在临界区外部就能得知临界资源的情况。

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

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

相关文章

c++中关于Thread Affinity(线程亲和性)示例源码

win10下&#xff0c;可以在任务管理器里面设置某个进程的线程亲和性,如下图: 然后选择相关的cpu&#xff0c;如下图&#xff1a; 这么做可以使得相关的线程在某些密集型计算任务中只会运行在某些指定的cpu上&#xff0c;以便提高性能。 以下是windwos上c程序中应用Thread Affi…

腾讯mini项目-【指标监控服务重构】2023-08-20

今日已办 PPT制作 答辩流程 概述&#xff1a;对项目背景、架构进行介绍&#xff08;体现我们分组的区别和需求&#xff09;人员&#xff1a;小组成员进行简短的自我介绍和在项目中的定位&#xff0c;分工进展&#xff1a;对项目进展介绍&#xff0c;其中a、b两组的区别和工作…

STM32——SPI通信

文章目录 SPI&#xff08;Serial Peripheral Interface&#xff09;概述&#xff1a;SPI的硬件连接&#xff1a;SPI的特点和优势&#xff1a;SPI的常见应用&#xff1a;SPI的工作方式和时序图分析&#xff1a;工作模式传输模式与时序分析工作流程 SPI设备的寄存器结构和寄存器设…

Linux四种I/O模型

一.四种模型 阻塞式IO&#xff0c;非阻塞式IO&#xff0c;信号驱动IO&#xff0c;IO多路复用 二.阻塞式IO 特点&#xff1a;最简单&#xff0c;最常用&#xff0c;效率低 阻塞I/O 模式是最普遍使用的I/O 模式 系统默认状态&#xff0c;套接字建立后所处于的模式就是阻塞I/O 模式…

国家网络安全周2023时间是什么时候?有什么特点?谁举办的?

国家网络安全周2023时间是什么时候&#xff1f; 2023年国家网络安全宣传周将于9月11日至17日在全国范围内统一开展。其中开幕式等重要活动将在福建省福州市举行。今年网安周期间&#xff0c;除开幕式外&#xff0c;还将举行网络安全博览会、网络安全技术高峰论坛、网络安全微视…

【Git】万字git与gitHub

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理在git和GitHub时的笔记与感言 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1faf0;&…

【FAQ】安防监控/视频汇聚/云存储/智能视频分析平台EasyCVR显示CPU过载,如何解决?

视频云存储/安防监控/视频汇聚平台EasyCVR基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频监控系统EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云…

线性代数的本质(四)——行列式

文章目录 行列式二阶行列式 n n n 阶行列式行列式的性质克拉默法则行列式的几何理解 行列式 二阶行列式 行列式引自对线性方程组的求解。考虑两个方程的二元线性方程组 { a 11 x 1 a 12 x 2 b 1 a 21 x 1 a 22 x 2 b 2 \begin{cases} a_{11}x_1a_{12}x_2b_1 \\ a_{21}x_…

无涯教程-JavaScript - COLUMNS函数

描述 COLUMNS函数返回数组或引用中的列数。 语法 COLUMNS (array)争论 Argument描述Required/OptionalarrayAn array or array formula, or a reference to a range of cells for which you want the number of Columns.Required Notes COLUMNS(1:1)返回Excel中的列数,即…

【python手写算法】numpy实现简易神经网络和反向传播算法【1】

import numpy as npdef dense(A,W):Znp.matmul(A,W)#矩阵乘法return 1/(1np.exp(-Z))if __name__ __main__:leanring_rate100Anp.array([[200.0,17.0]])# Wnp.array([[1,-3,5],# [-2,4,-6]])# bnp.array([[-1,1,2]])W1 np.array([[0., -10, 4],[-1,3,2]])W2np.ar…

STM32单片机——串口通信(轮询+中断)

STM32单片机——串口通信&#xff08;轮询中断&#xff09; 串口通信相关概念HAL库解析及CubeMX工程配置与程序设计常用函数介绍CubeMX工程配置HAL库程序设计&#xff08;轮询中断&#xff09;轮询数据收发中断收发数据 固件库程序设计及实现固件库配置流程结构体配置及初始化程…

React复习日志大纲

文章目录 React基础篇创建项目启动项目项目目录说明调整项目src剩余目录01基本使用02 列表渲染03 条件渲染04 样式处理05 函数和类组件创建和渲染06 事件绑定07 事件对象e08 传递额外参数09 组件状态修改10 受控组件11 非受控组件12 组件通信父传子13 Props说明14 组件通信子传…

Golang代码漏洞扫描工具介绍——govulncheck

Golang Golang作为一款近年来最火热的服务端语言之一&#xff0c;深受广大程序员的喜爱&#xff0c;笔者最近也在用&#xff0c;特别是高并发的场景下&#xff0c;golang易用性的优势十分明显&#xff0c;但笔者这次想要介绍的并不是golang本身&#xff0c;而且golang代码的漏洞…

Linux网络编程

一.协议 1.1什么是协议 从应用的角度出发&#xff0c;协议可理解为“规则”&#xff0c;是数据传输和数据的解释的规则。 假设&#xff0c;A、B双方欲传输文件。规定: 第一次&#xff0c;传输文件名&#xff0c;接收方接收到文件名&#xff0c;应答OK给传输方; 第二次&#xff…

【每日一题】34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09; 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 […

vscode-server

1know_host清除 2 删除服务器里的home/user/.vscode-server&#xff08;不是根root下的vscode-server&#xff09;&#xff0c;删除时用户名保持一致。 3 ssh配置文件 /etc/ssh/sshd_config[想改变,使用root&#xff0c;修改文件权限] 4 删除修改后&#xff0c;重启Windows下…

打造生产级Llama大模型服务

对于任何想要尝试人工智能或本地LLM&#xff0c;又不想因为意外的云账单或 API 费用而感到震惊的人&#xff0c;我可以告诉你我自己的旅程是如何的&#xff0c;以及如何开始使用廉价的消费级硬件执行Llama2 推理 。 这个项目一直在以非常活跃的速度发展&#xff0c;这使得它非…

父域 Cookie实现sso单点登录

单点登录&#xff08;Single Sign On, SSO&#xff09;是指在同一帐号平台下的多个应用系统中&#xff0c;用户只需登录一次&#xff0c;即可访问所有相互信任的应用系统。Cookie 的作用域由 domain 属性和 path 属性共同决定。在 Tomcat 中&#xff0c;domain 属性默认为当前域…

Python浪漫星空

系列文章 序号文章目录直达链接1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595.blog.csdn.net/article/details/1295031234漂浮爱心https://wan…

校园网web免认真,大量服务器

服务器加满了&#xff0c;没有几个人来&#xff0c;传点图片看实力 什么方法解web认证方式校园网&#xff1f; 一般的校园网是对学生免费开放的&#xff0c;假如你是学生输入学号密码上网就是了&#xff0c;假如你不是那就是想蹭网了&#xff0c;再假如你不想让管理员或上网行为…