​ 生产者消费者问题(条件变量 互斥锁)

  

   本篇文章对生产者消费者(模型)问题进行了详解。其中给出了基于阻塞队列的生产者消费者模型demo代码和对涉及到的条件变量与互斥锁的操作也进行了详细解释解释了条件变量等待时,为什么还需要一把锁的问题。对生产者消费者(模型)问题进行总结,且解释了生产者消费者(模型)问题效率提升的所在。希望本篇文章会对你有所帮助。

文章目录

一、生产者消费者模型

1、1 什么是生产者消费者模型

1、2 再次生产者消费者模型

1、2、1 生产者与生产者

1、2、2 消费者与消费者

1、2、3 消费者与生产者

二、条件变量

2、1 条件变量引入

2、1、1 线程同步

2、1、2 多线程竞争引起饥饿等问题

2、2 什么是条件变量

2、2、1 pthread_cond_t

2、2、1 pthread_cond_init

2、2、2 pthread_cond_wait

2、2、3 pthread_cond_signal

2、2、4 pthread_cond_broadcast

2、2、5 pthread_cond_destroy

三、生产者消费者模型demo代码

3、1 基于阻塞队列生产者消费者模型

3、2 派发任务不同

3、3 LockGuard(RAII)

四、生产者消费者模型总结

  生产者消费者模型的效率提升到底提升在哪里呢?


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:Linux从入门到精通  👀

💥 标题:生产者消费者(模型)问题💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️

一、生产者消费者模型

1、1 什么是生产者消费者模型

  生产者消费者模型是一种并发编程中常用的设计模式,也被称为生产者-消费者问题。它基于生产者和消费者之间共享一个缓冲区(是由一个数据结构来维护的),并通过该缓冲区来实现数据的交换和协调。

  在生产者消费者模型中,生产者负责生成数据或任务,并将其放入缓冲区,而消费者则从缓冲区中获取数据或任务并进行处理。这种模型主要解决了生产者和消费者之间数据交互的同步和协作问题。

  以下是生产者消费者模型的基本流程:

  1. 生产者生成数据或任务。
  2. 如果缓冲区未满,生产者将数据或任务放入缓冲区;否则,生产者等待直到缓冲区有空间。
  3. 消费者从缓冲区获取数据或任务。
  4. 如果缓冲区不为空,消费者处理数据或任务;否则,消费者等待直到缓冲区有数据。
  5. 当生产者将数据或任务放入缓冲区后,通知等待中的消费者继续处理。
  6. 当消费者处理完数据或任务后,通知等待中的生产者继续生成。

1、2 再次生产者消费者模型

  其实对上述的概念理解后,我们也不难发现:生产者和消费者彼此之间不直接通讯,而通过特定数据结构来进行通讯。所以生产者生产完数据之后不用等待消费者处理,直接扔给特定数据结构,消费者不找生产者要数据,而是直接从特定数据结构里取,而这个特定的数据结构就相当于一个缓冲区,平衡了生产者和消费者的处理能力,从而提高了效率,也完成了生产和消费的解耦

  对应的并发编程怎么理解呢?通俗理解:消费者就是由单线程或者多个线程来取数据,生产者是由单线程或者多个线程来生产数据,中间会有一个特定数据结构供消费者与生产者数据交换与协调。我们接着探究一下消费者和生产者之间的关系。

1、2、1 生产者与生产者

  其实我们不难发现:生产数据时,肯定是存在竞争关系的。其次为了保护数据安全,必须是互斥的。当然,只有一个消费者,也就是这有一个生产数据的线程时,不存在所谓的竞争关系。

1、2、2 消费者与消费者

  一个消费者与另一个消费者存在竞争关系吗?答案是存在的!假如两个消费者同时看到一份数据,那么由谁来取呢?同时也必须存在互斥关系,保证数据的安全。只有一个消费者,也就是只有一个消费数据的线程时,也不存在竞争关系。

1、2、3 消费者与生产者

  生产者和消费者之间的互斥与同步关系。所谓互斥,就是生产数据过程中,不能消费数据。目的就是保证读写安全。所谓同步,当缓冲区数据满了或空了,进行阻塞等待另一方通知,而不是一直在申请和释放资源,判断资源是否存在。

二、条件变量

2、1 条件变量引入

  上述的关系维护中,互斥关系可由互斥锁来维护,进而保证数据的安全。消费者与生产者的同步关系怎么维护呢?

2、1、1 线程同步

  线程同步是指在多线程编程中,通过各种机制来协调和控制线程的执行顺序,以确保它们按照特定的规则访问共享资源或执行特定的操作。线程同步的主要目的是避免竞争条件(Race Condition)和保证数据的一致性。下面我们看一下多线程竞争引起的不合理情况。

2、1、2 多线程竞争引起饥饿等问题

  我们看下图例子:

  这也是我们讲解互斥锁时候举的例子。当一个线程申请了锁资源后,他会首先会对临界资源进行检测。假如检测资源不存在,则会释放当前的锁资源。但是,由于进程时间片和优先级等问题,该线程仍然会取申请锁资源,然后再次进行检测、释放、申请……进入循环。当然,该线程被切换后依然可以与其他线程竞争锁资源!!!那么这个线程有错吗?并没有错误,只是不合理!!!

  该线程在竞争资源时,同时也频繁申请释放锁资源。有可能使其他线程申请不到资源,同时也处于等待状态,这也就造成了其他线程的饥饿问题。同时,该线程已经知道临界资源不存在后,还在频繁的申请资源,自己本身不就是一种浪费吗!!!

  这时就需要引入同步的情况,来控制线程的执行顺序。通俗理解线程同步:让线程按照一定顺序来执行。合理的情况是:当该线程检测到临界资源不存在后,可以进行等待。等待其他线程提供资源后,再将该线程唤醒。

  而条件变量就可以很好的控制线程的执行顺序

2、2 什么是条件变量

  当我们在申请临界资源前,首先要检测临界资源是否存在。检测的本质就是在访问临界资源。所以:对临界资源的检测也一定是需要在加锁解锁之间的

  而常规的对临界资源检测是否就绪,就必须频繁的申请和释放锁。那能不能在检测到临界资源条件不就绪时:不要再频繁申请资源和自己检测了,进行等待。同时当条件就绪时,由某一线程来通知该线程来申请临界资源和访问。条件变量就可以很好的做到

  在多线程编程中,条件变量是一种同步机制,它用于协调多个线程之间的操作。条件变量的作用是让线程在某个条件满足的情况下等待或唤醒

  具体来说,如果一个线程需要等待某个特定的条件被满足才能继续执行,那么该线程可以调用条件变量上的等待函数,这样线程会被阻塞,直到其他线程通过调用条件变量上的唤醒函数来唤醒它。条件变量可以避免线程在等待时浪费 CPU 资源,从而提高程序的效率

  此外,条件变量还可以用于解决多线程中的竞态条件问题。竞态条件是指多个线程在访问共享资源时发生争用的情况,如果不加以控制,容易导致不可预期的结果。条件变量可以和互斥量一起使用,通过在互斥量上加锁来保护共享资源的访问,并使用条件变量来等待或唤醒其他线程,从而避免竞态条件的发生

  总结来说,引入条件变量可以提供线程之间的协作和通信机制,避免竞争条件和死锁情况的发生,提高多线程程序的效率和可靠性。接下来看一下条件变量的相关操作的使用

2、2、1 pthread_cond_t

  pthread_cond_t是一个条件变量类型,用于线程间的同步和通信。它是POSIX线程库提供的一种机制,用于解决多个线程之间共享数据时可能出现的竞态条件问题。

  条件变量(Condition Variable)可以让线程在满足特定条件之前进入等待状态,并在条件发生变化时被唤醒。它通常与互斥锁(Mutex)结合使用,以确保在线程等待条件变量的同时,对共享资源进行安全的访问

  pthread_cond_t类型的变量需要通过一些函数来进行初始化、销毁和操作,常用的函数有:

  1. pthread_cond_init():用于初始化条件变量。参数为pthread_cond_t类型的指针,该函数会将条件变量初始化为默认值。在使用条件变量之前,必须先进行初始化。
  2. pthread_cond_destroy():用于销毁条件变量。参数为pthread_cond_t类型的指针,该函数会释放相关资源。
  3. pthread_cond_wait():用于等待条件变量满足特定条件,将线程挂起。参数包括条件变量和互斥锁,函数会自动释放互斥锁,并在接收到信号时重新获取互斥锁后继续执行。
  4. pthread_cond_signal():用于发送信号,唤醒一个等待条件变量的线程。参数为条件变量,会选择一个处于等待状态的线程进行唤醒。
  5. pthread_cond_broadcast():用于发送广播,唤醒所有等待条件变量的线程。参数为条件变量,会将所有等待的线程都唤醒。

  条件变量的使用流程一般如下:

  1. 创建并初始化互斥锁和条件变量。
  2. 持有互斥锁,访问共享资源。
  3. 如果某个条件不满足,调用pthread_cond_wait()等待条件满足,并释放互斥锁。
  4. 其他线程改变了条件后,通过pthread_cond_signal()或pthread_cond_broadcast()发送信号。
  5. 等待的线程被唤醒后,重新获取互斥锁,并继续执行之后的操作。

  条件变量的引入可以有效避免线程的忙等待,减少了系统资源的占用,并提高了程序的效率和性能。它是多线程编程中重要的同步机制之一,可以用于各种场景,如生产者-消费者模型、线程池等。

2、2、1 pthread_cond_init

  pthread_cond_init()函数用于初始化条件变量,它的函数原型如下:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • 返回值:函数执行成功返回0,失败返回错误码。
  • 参数:
    • cond:指向待初始化的条件变量的指针。
    • attr:指向条件变量属性的指针,通常可以将其设置为NULL,表示使用默认属性。

  上述情况是pthread_cond_t 对象是局部变量。当pthread_cond_t 对象是全局时,我们一般用:PTHREAD_COND_INITIALIZER。

2、2、2 pthread_cond_wait

  pthread_cond_wait()函数是用于等待条件变量的特定状态发生,并挂起线程的执行。pthread_cond_wait()函数有两个参数和一个返回值,具体含义如下:

  1. 参数cond:条件变量

    • 类型:pthread_cond_t*。
    • 简介:表示要等待的条件变量。
  2. 参数mutex:互斥锁

    • 类型:pthread_mutex_t*。
    • 简介:与条件变量结合使用,保证在等待前后对共享资源的访问是线程安全的。

  有很多人都有一个疑问:为什么使用条件变量等待时,还需要一把互斥锁呢?主要原因就是对临界资源的判断是在锁之间的。后文会结合实际应用对此详细解释

  需要注意的是,在pthread_cond_wait()函数中,线程被挂起,直到收到由其他线程调用pthread_cond_signal()或者pthread_cond_broadcast()发出的信号。并且,当线程被唤醒继续执行时,它会自动重新获得互斥锁,以继续对被保护资源进行访问。

2、2、3 pthread_cond_signal

  pthread_cond_signal()函数用于向指定正在等待条件变量的线程发送信号,以唤醒其线程继续执行。函数定义如下:

int pthread_cond_signal(pthread_cond_t *cond);

  返回值:

  • 如果成功发送信号,则返回0;
  • 如果发生错误,则返回非零错误代码。常见的错误包括传入无效的条件变量指针或未初始化的条件变量。

  下面我们结合一个例子来再次理解我们上述的所有概念。代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>int condition = 0;pthread_mutex_t mutex;
pthread_cond_t cond;
void* thread1(void* arg) {pthread_mutex_lock(&mutex);// 检查条件是否满足,如果不满足则等待条件变量while (condition == 0) {pthread_cond_wait(&cond, &mutex);}printf("Thread 1: Condition is now fulfilled.\n");pthread_mutex_unlock(&mutex);return NULL;
}void* thread2(void* arg) {pthread_mutex_lock(&mutex);// 修改条件为满足condition = 1;printf("Thread 2: Condition has been changed.\n");// 通知所有等待该条件的线程pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t tid1, tid2;// 初始化互斥锁和条件变量pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);// 创建线程1pthread_create(&tid1, NULL, thread1, NULL);// 创建线程2pthread_create(&tid2, NULL, thread2, NULL);// 等待线程1和线程2结束pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

  此示例中,创建了两个线程thread1和thread2,并在主函数中等待它们的结束。线程1等待条件变量condition满足后继续执行,而线程2修改condition为满足,并使用pthread_cond_signal()通知等待该条件的线程

  在thread1线程中,我们首先使用pthread_mutex_lock()锁住互斥锁mutex,然后使用while循环来检查条件是否满足。如果条件不满足,则调用pthread_cond_wait()函数来等待条件变量。调用pthread_cond_wait()函数进行阻塞等待后,回去执行其他线程(thread2)。但是线程thread1 还带着互斥锁呢!线程thread2怎么执行呢?

  其实在调用pthread_cond_wait()函数时,pthread_cond_wait()函数会自动释放互斥锁mutex并挂起线程的执行直到条件变量被其他线程发出信号来通知该线程条件已经满足。一旦收到通知,该线程会重新获取互斥锁并继续执行这正是等待时需要传入一把互斥锁的原因

  在thread2线程中,我们首先使用pthread_mutex_lock()锁住互斥锁mutex,然后修改条件变量condition为满足,并使用pthread_cond_signal()函数通知等待该条件的线程。最后,释放互斥锁并结束线程。

  大多数情况下,都是一个线程进入阻塞等待后,需要另一个线程检测临界资源是否满足条件,进而是否去唤醒指定等待的线程。当被唤醒的时候,还是在临界区唤醒。但是这里有一个细节:

  我们检测条件标量否满足时,采用的时循环式检测。为什么要采用循环式检测呢?直接采用一个if 判断不就完了。原因有如下两点

  • pthread_cond_wait() 也是一个函数调用,只要是函数调用,就有可能调用失败!
  • 当有多个线程进行等待时,且使用同一条件变量,这时候有肯能存在伪唤醒的情况!
  • 防止只用thread_cond_broadcast()进行唤醒。

2、2、4 pthread_cond_broadcast

  pthread_cond_broadcast()函数是用于广播条件变量,唤醒所有等待该条件变量的线程。函数的原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);
  1. 返回值:

    • 函数返回值为0表示成功。
    • 如果返回值为正数,则表示失败,并且返回的值对应于错误代码。
  2. 参数:

    • 参数cond:表示需要广播的条件变量。它是一个指向pthread_cond_t类型的指针,该类型在头文件pthread.h中定义。 注意:使用pthread_cond_broadcast()函数之前,条件变量必须使用pthread_cond_init()函数进行初始化。

2、2、5 pthread_cond_destroy

  pthread_cond_destroy()函数,该函数用于销毁条件变量,释放相关资源。函数的原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

  它的返回值是整型,并且有两种可能的取值:

  • 如果成功执行,返回值为0。
  • 如果出现错误,返回值为一个非零的错误码。

  参数详解如下:

  • pthread_cond_t *cond:指向条件变量的指针。该参数表示需要销毁的条件变量。

  在调用该函数之前,必须确保没有任何线程正在等待该条件变量。如果有线程在等待条件变量上阻塞,会导致未定义行为。因此,在调用pthread_cond_destroy()之前,必须先调用pthread_cond_broadcast()或pthread_cond_signal()函数唤醒所有等待的线程,或者使用其他同步机制来确保没有线程在等待条件变量。

  当条件变量销毁成功后,相关的资源将被释放,可以通过重新初始化(pthread_cond_init())来再次使用该条件变量。

三、生产者消费者模型demo代码

  当我们了解完上述的概念后,我们再来看一下生产者和消费者模型的德莫代码。

3、1 基于阻塞队列生产者消费者模型

  当我们在生产数据时,一定是有限制的。我们这里采用阻塞队列,当数据满的时候就不可再生产数据。当数据为空的时候,就不可再消费数据。我们先看一下阻塞队列的代码:

const int DefCapacity=5;
template<class T>
class BlockQueue
{
private:bool isEmpty(){return bq_.size()==0;}bool isFull(){return bq_.size()==capacity_;}
public:BlockQueue(int capacity=DefCapacity):capacity_(capacity){pthread_mutex_init(&mtx_,nullptr);pthread_cond_init(&Full_,nullptr);pthread_cond_init(&Empty_,nullptr);}void push(const T& in){pthread_mutex_lock(&mtx_);while(isFull())pthread_cond_wait(&Full_,&mtx_);bq_.push(in);pthread_cond_signal(&Empty_);pthread_mutex_unlock(&mtx_);}void pop(T* out){pthread_mutex_lock(&mtx_);while(isEmpty())pthread_cond_wait(&Empty_,&mtx_);*out=bq_.front();bq_.pop();pthread_cond_signal(&Full_);pthread_mutex_unlock(&mtx_);}~BlockQueue(){pthread_mutex_destroy(&mtx_);pthread_cond_destroy(&Empty_);pthread_cond_destroy(&Full_);}
private:queue<T> bq_;int capacity_;pthread_mutex_t mtx_;pthread_cond_t Full_;pthread_cond_t Empty_;
};void* consumer(){}
void* producter(){}
int main()
{BlockQueue<Task> *bqueue=new BlockQueue<Task>();pthread_t c,p;pthread_create(&c,nullptr,consumer,(void*)bqueue);pthread_create(&p,nullptr,producter,(void*)bqueue);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}

  BlockQueue类是一个模板类,支持线程安全的阻塞队列。它使用互斥锁(pthread_mutex_t)对队列的访问进行保护,同时使用条件变量(pthread_cond_t)进行线程间的同步。其中push用于向队列中添加元素,如果队列已满,则等待非满条件;pop用于从队列中取出元素,如果队列为空,则等待非空条件。我们使用了两个不同的条件变量和一把锁维护了生产者线程(Full_)与消费者线程(Empty_)的同步与互斥BlockQueue的容量由构造函数参数指定,默认为5。在析构函数中对使用的互斥锁和条件变量进行了销毁操作。 

  我们在main()函数中,创建一个BlockQueue<Task>对象作为生产者和消费者之间的共享阻塞队列。然后创建两个线程,分别执行consumerproducter函数,将共享队列作为参数传递给这两个线程。最后使用pthread_join函数等待线程的结束,释放线程资源。

3、2 派发任务不同

  当我们学习到了生产者消费者模型后,他是有类模板来实现的。所以我们生产不同的任务类型。具体如下:

  • 生产消费数据类型为int:
    BlockQueue<int> *bqueue=new BlockQueue<int>();void* consumer(void* args)
    {BlockQueue<int> *bqueue=(BlockQueue<int>*) args;while(true){int a;bqueue->pop(&a);cout<<"consumer use data: "<< a <<endl;sleep(1);}return nullptr;
    }void* producter(void* args)
    {BlockQueue<int> *bqueue=(BlockQueue<int>*) args;int a=100;while(true){bqueue->push(a);cout<<"producter make data: "<<a<<endl;a--;}return nullptr;
    }
  • 生产消费数据类型为class:
    typedef std::function<int(int, int)> func_t;class Task
    {public:Task(){}Task(int x, int y, func_t func):x_(x), y_(y), func_(func){}int operator ()(){return func_(x_, y_);}
    public:int x_;int y_;// int type;func_t func_;
    };BlockQueue<Task> *bqueue=new BlockQueue<Task>();int myAdd(int x, int y)
    {return x + y;
    }void* consumer(void* args)
    {BlockQueue<Task> *bqueue=(BlockQueue<Task>*) args;while(true){Task t;bqueue->pop(&t);std::cout <<" consumer use data: "<< t.x_ << "+" << t.y_ << "=" << t() << std::endl;}return nullptr;
    }void* producter(void* args)
    {BlockQueue<Task> *bqueue=(BlockQueue<Task>*) args;while(true){int x = rand()%10 + 1;usleep(rand()%1000);int y = rand()%5 + 1;// int x, y;// std::cout << "Please Enter x: ";// std::cin >> x;// std::cout << "Please Enter y: ";// std::cin >> y;Task t(x, y, myAdd);bqueue->push(t);cout<<"producter make data: "<<t.x_ << "+" << t.y_ << " = ?"<<endl;sleep(1);}return nullptr;
    }
    
  • 当然,生产和消费的数据可以是任意类型。我们只需要修改所线程所对应的函数就行。

3、3 LockGuard(RAII)

  当然,我们也可采用RAII的思想对互斥锁进行封装。这样我们就不需要担心释放锁资源的问题了。具体代码如下:

class Mutex
{
public:Mutex(pthread_mutex_t *mtx):pmtx_(mtx){}void lock() {std::cout << "要进行加锁" << std::endl;pthread_mutex_lock(pmtx_);}void unlock(){std::cout << "要进行解锁" << std::endl;pthread_mutex_unlock(pmtx_);}~Mutex(){}
private:pthread_mutex_t *pmtx_;
};// RAII风格的加锁方式
class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx):mtx_(mtx){mtx_.lock();}~lockGuard(){mtx_.unlock();}
private:Mutex mtx_;
};

四、生产者消费者模型总结

  生产者消费者模型是一种常见的并发编程模式,用于处理生产者和消费者之间的数据共享和同步。它可以有效地解决在多线程或多进程环境下,生产者与消费者之间的数据竞争和资源冲突问题。下面是对生产者消费者模型的总结:

  1. 模型介绍: 生产者消费者模型涉及两个主要角色:生产者和消费者。同时包含了一个交易场所:仓库(特定的数据结构,也可视为数据缓冲区)生产者负责生成数据并将其放入共享缓冲区,而消费者负责从共享缓冲区中获取数据并进行处理。

  2. 共享缓冲区: 共享缓冲区是生产者和消费者之间进行数据交换的中介。生产者将数据放入缓冲区,消费者从缓冲区中获取数据。缓冲区可以是有界(固定大小)或无界(动态增长)的。

  3. 同步与互斥: 为了保证生产者和消费者之间的正确协作,需要使用同步机制和互斥机制来处理数据的操作和访问。同步机制用于确保生产者和消费者之间的正确顺序执行,互斥机制用于防止多个线程同时访问共享资源。

  4. 解决方案: 有多种方式可以实现生产者消费者模型,其中包括使用信号量、互斥锁、条件变量等同步和互斥机制。常见的解决方案包括使用线程库提供的线程同步原语,如pthread库中的pthread_cond_t、pthread_mutex_t等。

  5. 解决方法的选择: 在选择解决方案时,需要考虑具体的应用场景和需求。根据并发程度、性能要求、资源限制等因素,可以选择合适的解决方案。同时,还需注意解决方案的正确性、可靠性和可维护性。

  6. 避免常见问题: 在实现生产者消费者模型时,需要注意避免常见的问题,如死锁(deadlock)、资源饥饿(resource starvation)和活锁(live lock)。这些问题可能导致程序无法正常执行或性能下降。

  生产者消费者模型的效率提升到底提升在哪里呢?

  我们学完生产者消费者模型后,发现生产者和消费者也是需要竞争锁资源的。简单来说,即使有多个生产者和多个消费者(多线程),最终不也是串行访问临界资源吗? 当然,因为条件变量的使用,可以在一定程度上提高效率。但是这并不明显,因为本质上还是在串行执行。

  我们不应该局限于模型本身上。试想正常情况下数据的来源需要耗费时间吗?数据的处理需要消耗时间吗?答案是要的!生产者消费者模型给我们提供了数据缓冲区,从而实现了生产者与消费者的解耦。此时我们应该分清生产数据和向缓冲区放数据是两个概念生产者消费者模型本省是放数据和拿数据。当然,在放数据和拿数据时,也并不影响我们放数据前的生产数据和拿数据后的对数据的处理。这样也就从一定意义上实现了生产数据和对数据处理的并发!从而大大提高了效率。

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

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

相关文章

CSS核心使用

CSS核心使用 box-sizingbox-shdowtext-shadowpositionwriting-mode box-sizing 定义计算一个元素的总高度和总宽度. 属性值 content-box 默认值,width 内容宽度,height内容的高度border-box 宽度和高度包含内容,内边距和边框 widthborderpadding内容宽度, heightborderpaddi…

【AIGC】文心一言 - 生成中秋祝福语、诗词、图像、相关代码等

文心一言 - 生成中秋祝福词、诗、画 引言一、中秋祝福语二、中秋诗词三、中秋图像 - 闪亮的月亮四、中秋代码 引言 文心一言是百度推出国产聊天GPT产品&#xff0c;不仅可以与人对话互动&#xff0c;回答问题&#xff0c;协助创作&#xff0c;还能进行数据分析、逻辑判断、绘画…

无涯教程-JavaScript - INT函数

描述 INT函数将数字四舍五入到最接近的整数。 语法 INT (number)争论 Argument描述Required/OptionalNumberThe real number you want to round down to an integer.Required 适用性 Excel 2007,Excel 2010,Excel 2013,Excel 2016 Example JavaScript 中的 INT函数 - 无…

【前端知识】Three 学习日志(十二)—— WebGL渲染器设置(锯齿模糊)

Three 学习日志&#xff08;十二&#xff09;—— WebGL渲染器设置(锯齿模糊) 一、设置抗锯齿 const renderer new THREE.WebGLRenderer({antialias:true, });二、效果对比 设置前&#xff1a; 设置后&#xff1a; 三、查看并设置设备像素比 // 不同硬件设备的屏幕的设…

从零开始搭建java web springboot Eclipse MyBatis jsp mysql开发环境

文章目录 1 第一步软件安装1.1 下载并安装Eclipse1.2 下载并安装Java1.3 下载并安装Apache Maven1.4 下载并安装MySQL 2 创建所需要的表和数据3 创建Maven 工程、修改jdk4 通过pom.xml获取所需要的jar包5 安装Eclipse的MyBatis插件6 创建文件夹以及jsp文件7 创建下面各种java类…

内网穿透的应用-Cloudreve搭建云盘系统,并实现随时访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

go net/http 源码解读

回顾 1. HTTP Server 在 go 中启动一个 http server 只需短短几行代码 func PingHandler(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "pong!") }func main() {http.HandleFunc("/ping", PingHandler)log.Fatal(http.ListenAndServe(&…

【文末赠书】SRE求职必会 —— 可观测性平台可观测性工程(Observability Engineering)

文章目录 〇、导读一、实现可观测性平台的技术要点是什么&#xff1f;二、兼容全域信号量三、所谓全域信号量有哪些&#xff1f;四、统一采集和上传工具五、统一的存储后台六、自由探索和综合使用数据七、总结★推荐阅读《可观测性工程》直播预告直播主题直播时间预约直播 视频…

K8S名称空间和资源配额

Kubernetes 支持多个虚拟集群&#xff0c;底层依赖于同一个物理集群。 这些虚拟集群被称为名称空间。名称空间namespace是k8s集群级别的资源&#xff0c;可以给不同的用户、租户、环境或项目创建对应的名称空间&#xff0c;例如&#xff0c;可以为test、dev、prod环境分别创建各…

使用Linkerd实现流量管理:学习如何使用Linkerd的路由规则来实现流量的动态控制

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

video 视频编解码一些debug方法

文章目录 一、通过命令去获取一些数据1.2 确定我们xml配置文件: 二、查看我们芯片支持的编码能力三、通过log去获取信息 这个文章的主要内容是为了后期性能方面的debug, 设计到前期的bringup则没有 一、通过命令去获取一些数据 获取媒体相关的参数&#xff1a; # getprop |…

Postman —— post请求数据类型

1、Postman中post的数据类型 post中有以下数据类型 1、form-data 2、x-www-form-urlencoded 3、raw 4、binary 2、Postman请求不同的post数据类型 from-data multipart/form-data&#xff0c;它将表单的数据组织成Key-Value形式&#xff0c;也可以上传文件&#xff0c;当…

基于elelemt-ui封装一个表单

子组件 searchForm <template><el-formref"form":model"value":rules"rules":label-width"labelWidth":inline"inline"><el-form-itemv-for"field in fields":key"field.slot":label&q…

基于5G网络的智能车间MES设计方案

导读 在数字化转型建设过程中&#xff0c;机械制造企业的难点是智能车间的设计。随着5G技术的发展&#xff0c;大量的带网络接口的智能设备的投入使用&#xff0c;智能车间的实施成为可能。本文从车间生产计划调度、过程管控、设备管理、质量管理、能耗管理和物流仓储等方面提…

[网鼎杯 2020 朱雀组]Nmap 通过nmap写入木马 argcmd过滤实现逃逸

这道题也很好玩 啊 原本以为是ssrf 或者会不会是rce 结果是通过nmap写入木马 我们来玩一下 传入木马 映入眼帘是nmap 我们首先就要了解nmap的指令 Nmap 相关参数-iL 读取文件内容&#xff0c;以文件内容作为搜索目标 -o 输出到文件-oN 标准保存-oX XML保存-oG Grep保存-oA…

埃文科技受邀出席“安全堤坝”技术论坛

2023年9月11日&#xff0c;2023年国家网络安全宣传周河南省活动开幕式暨河南省网络文明大会在开封博物馆开幕。由CCF YOCSEF郑州举办的“聚焦数据交易监管技术&#xff0c;筑牢数据交易‘安全堤坝’”技术论坛在开封市博物馆二楼会议厅举行。埃文科技总经理王永博士与副总经理武…

如何让照片动起来?几个制作方法和注意事项分享

在现代社交媒体的时代&#xff0c;我们经常会看到一些有趣、生动的动态照片。那么小伙伴们知道如何让照片动起来吗&#xff1f;下面将分享几个简单的制作方法和注意事项。 1.使用书单视频王&#xff1a;我们在手机中打开这款应用&#xff0c;进入首页后可以看到照片动起来功能&…

自动化办公更简单了:新版python-office,有哪些更新?

#职场经验谈# 大家好&#xff0c;这里是程序员晚枫&#xff0c;小破站/小红薯都叫这个名。 去年4月开源了一个Python自动化办公项目&#xff1a;python-office&#xff0c;GitHub和Gitee都能看到。1行代码实现复杂的自动化办公任务&#xff0c;帮助不懂代码的小白&#xff0c;…

oppo手机换荣耀手机,便签数据怎么搬家?

有不少网友之前使用的是OPPO手机&#xff0c;但是在使用了三四年之后就需要再次更换手机了&#xff0c;这时候一部分用户会选择更换相同品牌的OPPO手机&#xff0c;还有一部分用户会选择使用其他品牌的手机&#xff0c;例如小米、荣耀、华为等品牌。有的网友表示自己从OPPO手机…

Redis新篇一:认识Redis

首先&#xff0c;很抱歉小伙伴们&#xff0c;前段时间一直都没有更新&#xff0c;我很抱歉&#xff0c;现在开始持续更新Redis相关内容啦&#xff01;有需要的小伙伴们可以持续关注一下小博主的新篇哦~ 希望对你们有帮助&#xff01; 作者&#xff1a;爱撸猫的程序员 博客地址…