【Linux】线程同步和生产者-消费者模型

目录

    • 一. 线程同步
      • 1. 条件变量
      • 2. 条件变量接口
        • 条件变量的创建及初始化
        • 条件变量的销毁
        • 条件变量等待
        • 条件变量唤醒
      • 3. 条件变量同步解决抢占问题
    • 二. 生产者-消费者模型
      • 1. 什么是生产者-消费者模型
      • 2. 为什么要使用生产者-消费者模型
      • 3. 生产者-消费者模型特点
      • 4. 基于阻塞队列实现生产者-消费者模型
        • 单生产-单消费
        • 多生产-多消费
      • 4. POSIX 信号量
        • 信号量的初始化及销毁
        • 信号量的申请及释放
      • 5. 基于环形队列实现生产者-消费者模型
        • 单生产-单消费
        • 多生产-多消费

一. 线程同步

线程同步: 在保证数据安全的前提下, 使线程能够按照某种特定的顺序访问临界资源,避免饥饿问题;

例:
当去掉休眠后, 1 号线程由于先运行, 竞争锁的能力比较强, 直接抢占了大部分的资源;

#include "Thread.hpp"pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
int tickets = 50;void* func(Thread<int*>* td, int* tickets)
{while (1){//sleep(1);   // 休眠, 避免一个线程直接抢完LockGuard guard(&mtx);  // 申请加锁, 离开作用域自动解锁if (*tickets > 0)cout << "次线程: " << td->get_name() << ",  " << (*tickets)-- << endl;elsebreak;}return 0;
}int main()
{int n = 10;vector<Thread<int*> > threads;for (int i=1; i<n; i++)threads.emplace_back(func, &tickets, "thread-"+to_string(i));for (int i=1; i<n; i++)threads[i-1].start();for (int i=1; i<n; i++)threads[i-1].join();cout << "---------" << endl;cout << tickets << endl;return 0;
}

在这里插入图片描述
线程运行是没有问题的, 但是不符合期望, 在原生线程库中提供了条件变量这种方式来实现线程同步;

1. 条件变量

条件变量相当于一个队列, 若线程不满足运行条件, 那么推入当前条件变量的队列当中, 等待唤醒; 当其他线程唤醒时, 从当前条件变量的队列中推出线程; 通常条件变量和互斥锁同时使用;

条件变量与互斥锁不同, 互斥锁是线程自动竞争锁资源, 而条件变量是诱发的;

在这里插入图片描述

2. 条件变量接口

条件变量的创建及初始化

条件变量的类型为 pthread_cond_t, 在创建后需要进行初始化;

#include <pthread.h>// 定义条件变量
pthread_cond_t cond;// 全局/静态的条件变量初始化
cond = PTHREAD_COND_INITIALIZER;// 条件变量初始化
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

参数:

  • cond: 需要条件变量的指针;
  • cond_attr: 初始化时的相关属性, 设置为 nullptr 表示使用默认属性;

返回值:

  • 若成功, 返回 0; 若失败, 返回 error number;

全局/静态的条件变量和互斥锁相同, 也可以使用静态初始化, 自动初始化, 自动销毁;

条件变量的销毁
#include <pthread.h>int pthread_cond_destroy(pthread_cond_t *cond);

参数:

  • cond: 条件变量的地址;

返回值:

  • 若成功, 返回 0; 若失败, 返回 error number;
条件变量等待

pthread_cond_wait() 函数, 将等待当前线程, 并且释放当前线程申请的锁资源(避免当前锁资源出现死锁, 唤醒时自动竞争锁资源);
pthread_cond_timedwait() 函数, 和 pthread_cond_wait() 函数相同, 不过会限制等待时间, 超时自动唤醒, 避免死锁;

#include <pthread.h>int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

参数:

  • cond: 条件变量的地址;
  • mutex: 互斥锁的地址;
  • abstime: 指定等待的时间(其值为系统时间 + 等待时间);

返回值:

  • 若成功, 返回 0; 若失败, 返回 error number;
条件变量唤醒

pthread_cond_signalt() 函数, 唤醒指定条件变量等待的队头线程;

#include <pthread.h>int pthread_cond_signal(pthread_cond_t *cond);

参数:

  • cond: 条件变量的地址;

返回值:

  • 若成功, 返回 0; 若失败, 返回 error number;

pthread_cond_broadcast() 函数, 唤醒指定条件变量等待的所有线程;

#include <pthread.h>int pthread_cond_broadcast(pthread_cond_t *cond);

参数:

  • cond: 条件变量的地址;

返回值:

  • 若成功, 返回 0; 若失败, 返回 error number;

3. 条件变量同步解决抢占问题

将线程推入同一条件变量队列中, 一个一个的唤醒, 这样就保证了资源分配均匀;

  • Thread.hpp
#pragma once
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <functional>
#include <vector>using namespace std;class LockGuard
{public:LockGuard(pthread_mutex_t* mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t* _mutex;
};template<class T>
class Thread
{typedef function<void*(Thread<T>*, T&)> func_t;public:Thread(func_t func = nullptr, const T& args = T(), const string& name = "none"):_tid(0), _func(func), _args(args), _name(name){}static void* threadroutine(void* td){auto it = static_cast<Thread<T>*>(td);it->_func(it, it->_args);return 0;}bool start(){int flag = pthread_create(&_tid, nullptr, threadroutine, this);if (flag){_tid = 0;return false;}return true;}void join(){if (_tid){void* msg;int flag = pthread_join(_tid, &msg);if (flag){cerr << _name << "  join fail "<< endl;exit(1);}}_tid = 0;}~Thread(){if (_tid)join();}const string& get_name(){ return _name; }private:pthread_t _tid;func_t _func;string _name;T _args;
};
  • test.cpp
#include "Thread.hpp"pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int tickets = 50;void* func(Thread<int*>* td, int* tickets)
{LockGuard guard(&mtx);  // 申请加锁, 离开作用域自动解锁while (*tickets){cout << "次线程: " << td->get_name() << ",  " << (*tickets)-- << endl;pthread_cond_wait(&cond, &mtx);	// 线程等待, 自动释放锁资源, 下一个线程此时获取锁资源pthread_cond_signal(&cond);		// 唤醒下一个线程}return 0;
}void wait(vector<Thread<int*> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].join();
}void start(vector<Thread<int*> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].start();sleep(2);pthread_cond_signal(&cond);
}void init(vector<Thread<int*> >& threads, int n)
{for (int i=0; i<n; i++)threads.emplace_back(func, &tickets, "thread-"+to_string(i+1));
}int main()
{int n = 10;vector<Thread<int*> > threads;init(threads, n);start(threads, n);wait(threads, n);cout << "---------" << endl;cout << tickets << endl;return 0;
}

在这里插入图片描述

二. 生产者-消费者模型

1. 什么是生产者-消费者模型

假设有两个线程 A, B 和一个缓冲区; A 线程向缓冲区中写入数据, B 线程从缓冲区中读取数据, 这就是一个简单的生产者-消费者模型, A 为生产者, B 为消费者;
在这里插入图片描述

2. 为什么要使用生产者-消费者模型

  • 解耦
    假设生产者和消费者分别是两个类; 若使生产者直接调用消费者的某个方法, 那么生产者对于消费者就会产生依赖(也就是耦合); 若消费者的代码改变, 就可能会影响到生产者; 而若两者不直接调用或通信, 两者之间也就不会直接依赖, 耦合也就相应降低了;

  • 支持并发
    假设生产者直接调用消费者的某个方法, 由于函数调用是同步的, 那么生产者就需要等待消费者处理方法; 而在生产者-消费者模型中, 两者为并发的线程/进程, 只需要关心缓冲区的状态, 在缓冲区 非空&&非满 的情况下, 不会互相影响;

  • 支持忙闲不均

3. 生产者-消费者模型特点

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

  • 生产者与消费者的关系:
    同步: 当缓冲区满的时候, 生产者会进入休眠状态, 当下次消费者开始消耗缓冲区的数据时, 生产者才会被唤醒, 开始往缓冲区中添加数据; 当缓冲区空的时候, 消费者也会进入休眠状态, 直到生产者往缓冲区中添加数据时才会被唤醒;
    互斥: 同一时间, 生产者或消费者只能有一方向缓冲区添加或消耗数据;

  • 生产者与生产者的关系: 互斥;

  • 消费者与消费者的关系: 互斥;

4. 基于阻塞队列实现生产者-消费者模型

阻塞队列(Blocking Queue)是一种常用于实现生产者-消费者模型的数据结构;

阻塞队列的特定: 阻塞队列的是有容量的;

使用阻塞队列实现的生产者-消费者模型类似管道; 其同步和互斥特性使用条件变量和互斥锁实现;
在这里插入图片描述

单生产-单消费

根据生产者-消费者模型特点, 搭建出所需的框架;

#include "Thread.hpp"template<class T>
class BlockingQueue
{
public:BlockingQueue(int cap = 10):_cap(cap){// 初始化锁和条件变量pthread_mutex_init(&_mutex);pthread_cond_init(&_product_cond);pthread_cond_init(&_consum_cond);}void Push(const T& data){ }const T& Pop(){ }bool IsFull(){ return _blcok_queue.size() == _cap; }bool IsEmpty(){ return _blcok_queue.size() == 0; }~BlockingQueue(){// 销毁锁和条件变量pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}private:queue<T> _blcok_queue;int _cap;pthread_mutex_t _mutex;  // 消费者之间的互斥锁pthread_cond_t _product_cond;   // 生产者的条件变量pthread_cond_t _consum_cond;    // 消费者的条件变量
};

当生产者生产数据时, 也就是插入数据时, 条件为是否存在空间, 若没有, 就需要阻塞等待; 若有空间, 那么直接插入数据, 并且需要通知消费者插入了数据;

void Push(const T &in)
{pthread_mutex_lock(&_mutex); // 申请加锁if (IsFull()) // 插入判满pthread_cond_wait(&_product_cond, &_mutex);_blcok_queue.push(in); // 插入数据pthread_cond_signal(&_consum_cond);    // 唤醒消费者pthread_mutex_unlock(&_mutex); // 解锁
}

消费者同理

void Pop(T &out)
{pthread_mutex_lock(&_mutex);if (IsEmpty()) // 删除判空pthread_cond_wait(&_consum_cond, &_mutex);out = _blcok_queue.front();_blcok_queue.pop(); // 插入数据pthread_cond_signal(&_product_cond);    // 唤醒生产者pthread_mutex_unlock(&_mutex);}

创建测试, 生产者先运行 2 秒, 然后消费者开始消费;

#include "BlockingQueue.hpp"template<class T>
void* Product(Thread<T>* self, T& args)
{BlockingQueue<int>* blcok_queue = (BlockingQueue<int>*)args;int n = 1;while (1){cout << self->get_name() << " " << n << endl;cout << "---------" << endl;blcok_queue->Push(n++);sleep(1);}return 0;
}template<class T>
void* Consum(Thread<T>* self, T& args)
{int n;BlockingQueue<int>* blcok_queue = (BlockingQueue<int>*)args;while (1){blcok_queue->Pop(n);cout << self->get_name() << " " << n << endl;cout << "---------" << endl;sleep(1);}return 0;
}template<class T>
void Wait(vector<Thread<T> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].join();
}template<class T>
void Start(vector<Thread<T> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].start();//sleep(2);
}template<class T>
void Init(vector<Thread<T> >& threads, int n, T func(Thread<T>*,T&), void* data = nullptr, const char* name = "thread-")
{for (int i=0; i<n; i++)threads.emplace_back(func, data, name+to_string(i+1));
}int main()
{int n = 1;int m = 1;// vector<Thread<void*> > threads;BlockingQueue<int> blcok_queue;vector<Thread<void*> > product;vector<Thread<void*> > consum;Init(product, n, Product, &blcok_queue, "product-");Init(consum, m, Consum, &blcok_queue, "consum-");Start(product, n);Start(consum, m);Wait(product, n);Wait(consum, m);cout << "---------" << endl;return 0;
}

在这里插入图片描述

多生产-多消费

在单生产-单消费模型中, 插入和删除数据的条件判断使用的 if 判断, 那么当 pthread_cond_wait() 函数被唤醒时, 就会直接向下执行; 这种判断在实际上是有误的, 因为 pthread_cond_wait() 函数可能存在调用失败(误唤醒, 伪唤醒)的情况;

而在 多生产-多消费 模型中, pthread_cond_wait() 函数调用失败 或 调用pthread_cond_broadcast() 函数导致非法唤醒的情况更多, 所以需要将条件判断的 if 改为 while, 持续进行条件判断, 不合法的唤醒需要重新堵塞等待;

void Push(const T &in)
{pthread_mutex_lock(&_mutex); // 申请加锁while (IsFull()) // 插入判满pthread_cond_wait(&_product_cond, &_mutex);_blcok_queue.push(in); // 插入数据pthread_cond_signal(&_consum_cond);    // 唤醒消费者pthread_mutex_unlock(&_mutex); // 解锁
}void Pop(T &out)
{pthread_mutex_lock(&_mutex);while (IsEmpty()) // 删除判空pthread_cond_wait(&_consum_cond, &_mutex);out = _blcok_queue.front();_blcok_queue.pop(); // 插入数据pthread_cond_signal(&_product_cond);    // 唤醒生产者pthread_mutex_unlock(&_mutex);
}

在这里插入图片描述

4. POSIX 信号量

信号量的本质就是一个计数器; 信号量的 PV 操作是原子的, 可以使用信号量实现l临界资源的互斥和同步;

信号量主要作用是描述临界资源中的资源数目;

  • 若申请信号量成功, 计数器 - - (P 操作)
  • 若释放信号量成功, 计数器 ++ (V 操作)

若将临界资源看作一个整体, 这种信号量就是二元信号量, 类似互斥锁, 信号量只有两种状态: 0, 1;
在这里插入图片描述

若将临界资源中的资源数目划分为多份, 这种信号量就是多元信号量, 类似条件变量, 只有申请资源成功的, 才可以进行临界区操作;
在这里插入图片描述

信号量的初始化及销毁
#include <semaphore.h>// 创建信号量
sem_t sem;	// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);// 销毁信号量
int sem_destroy(sem_t *sem);

参数:

  • sem: 指定的信号量;
  • pshared: 当前信号量的共享状态. 传递 0 表示线程间共享, 传递 非0 表示进程间共享;
  • value: 信号量的初始值, 相当于资源的数目;

返回值: 若成功返回 0; 若失败返回 -1, 并设置错误码;

信号量的申请及释放
#include <semaphore.h>// 申请信号量
int sem_wait(sem_t *sem);		// 堵塞等待直至申请成功
int sem_trywait(sem_t *sem);	// 只会申请一次
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);	// 若失败, 等待 abs_timeout 后再次申请;// 释放信号量
int sem_post(sem_t *sem);

参数:

  • sem: 指定的信号量;
  • abs_timeout: 休眠时间, 若申请失败, 会在 abs_timeout 后再次申请;

返回值: 若成功返回 0; 若失败返回 -1, 并设置错误码;

5. 基于环形队列实现生产者-消费者模型

生产者-消费者模型的缓冲区也可以使用环形队列进行实现;
在这里插入图片描述

在环形队列中, 生产者只关心是否有空间放数据, 消费者只关心是否能从空间中取到数据, 只要申请资源成功, 生产者可以和消费者并发访问环形队列;
那么可以分别记录两者的下标, 若下标位置不同, 那么双方一定是有资源的;
在这里插入图片描述

若下标位置相同, 那么只可能为一方空, 一方满;
在这里插入图片描述

单生产-单消费

单生产-单消费模型中, 当两者信号量都不为 0 时, 两者可以并发执行;
当生产者信号量为 0 时, 生产者阻塞等待, 等待消费者消费; 当消费者信号量为 0 时, 消费者会阻塞等待, 等待生产者生产; 当对方生产/消费后, 自己就会唤醒, 保证了同步和互斥;

#include "Thread.hpp"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 = 10): _cap(cap), _pro_pos(0), _con_pos(0){_queue.resize(cap);// 初始化信号量sem_init(&_pro_sem, 0, cap);sem_init(&_con_sem, 0, 0);}void Push(const T &in){P(_pro_sem);    // 申请信号量// 至当前位置, 一定申请成功, 就一定会有资源_queue[_pro_pos++] = in; // 插入数据_pro_pos %= _cap;V(_con_sem);    // 释放信号量, 但释放的是消费者的信号量, 增加消费者可用资源数目}void Pop(T &out){P(_con_sem);    // 申请信号量// 至当前位置, 一定申请成功, 就一定会有资源out = _queue[_con_pos++]; // 删除数据_con_pos %= _cap;V(_pro_sem);    // 释放信号量, 但释放的是生产者的信号量, 增加生产者可用资源数目}~RingQueue(){// 销毁信号量sem_destroy(&_pro_sem);sem_destroy(&_con_sem);}private:vector<T> _queue;int _cap;size_t _pro_pos; // 生产者下标size_t _con_pos; // 消费者下标sem_t _pro_sem;  // 生产者的信号量sem_t _con_sem;  // 消费者的信号量
};
#include "BlockingQueue.hpp"
#include "RingQueue.hpp"LockGuard guard;template<class T>
void* Product(Thread<T>* self, T& args)
{RingQueue<int>* _queue = (RingQueue<int>*)args;int n = 1;while (1){cout << self->get_name() << " " << n << endl;cout << "---------" << endl;_queue->Push(n++);sleep(1);}return 0;
}template<class T>
void* Consum(Thread<T>* self, T& args)
{int n;RingQueue<int>* _queue = (RingQueue<int>*)args;while (1){_queue->Pop(n);cout << self->get_name() << " " << n << endl;cout << "---------" << endl;sleep(1);}return 0;
}template<class T>
void Wait(vector<Thread<T> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].join();
}template<class T>
void Start(vector<Thread<T> >& threads, int n)
{for (int i=0; i<n; i++)threads[i].start();//sleep(2);
}template<class T>
void Init(vector<Thread<T> >& threads, int n, T func(Thread<T>*,T&), void* data = nullptr, const char* name = "thread-")
{for (int i=0; i<n; i++)threads.emplace_back(func, data, name+to_string(i+1));
}int main()
{int n = 1;int m = 1;// vector<Thread<void*> > threads;//BlockingQueue<int> blcok_queue;RingQueue<int> _queue;vector<Thread<void*> > product;vector<Thread<void*> > consum;Init(product, n, Product, &_queue, "product-");Init(consum, m, Consum, &_queue, "consum-");Start(product, n);Start(consum, m);Wait(product, n);Wait(consum, m);cout << "---------" << endl;return 0;
}

在这里插入图片描述

多生产-多消费

但在多生产-多消费中需要注意, 由于生产者和生产者, 消费者和消费者之间存在互斥关系, 所以需要增加两把锁;

#include "Thread.hpp"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 = 10): _cap(cap), _pro_pos(0), _con_pos(0){_queue.resize(cap);// 初始化信号量sem_init(&_pro_sem, 0, cap);sem_init(&_con_sem, 0, 0);}void Push(const T &in){P(_pro_sem);       // 申请信号量_pro_mutex.Lock(); // 申请加锁// 至当前位置, 一定申请成功_queue[_pro_pos++] = in; // 插入数据_pro_pos %= _cap;_pro_mutex.Unlock(); // 申请解锁V(_con_sem);         // 释放信号量, 但释放的是消费者的信号量, 增加消费者可用资源数目}void Pop(T &out){P(_con_sem);       // 申请信号量_con_mutex.Lock(); // 申请加锁// 至当前位置, 一定申请成功out = _queue[_con_pos++]; // 删除数据_con_pos %= _cap;_con_mutex.Unlock(); // 申请解锁V(_pro_sem);         // 释放信号量, 但释放的是生产者的信号量, 增加生产者可用资源数目}~RingQueue(){// 销毁信号量sem_destroy(&_pro_sem);sem_destroy(&_con_sem);}private:vector<T> _queue;int _cap;size_t _pro_pos; // 生产者下标size_t _con_pos; // 消费者下标sem_t _pro_sem; // 生产者的信号量sem_t _con_sem; // 消费者的信号量LockGuard _pro_mutex; // 生产者的信号量LockGuard _con_mutex; // 消费者的信号量
};

在这里插入图片描述

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

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

相关文章

技术前沿:三品PLM系统引领工程变更管理新趋势

引言 在当今快速变化的制造行业&#xff0c;产品生命周期管理&#xff08;PLM&#xff09;系统已成为企业不可或缺的工具之一。PLM系统不仅帮助企业优化产品开发流程&#xff0c;还对工程变更管理&#xff08;ECM&#xff09;起着至关重要的作用。本文将探讨PLM系统在工程变更…

解决ssh报错,.ssh/id_rsa: No such file or directory Permission denied (publickey)

拉取依赖或者代码时说没有权限 首先我们可以看到的是这个报错但是我们的远程确实配置ssh密钥 首先我们可以看到的是这个报错 但是我们的远程确实配置ssh密钥 我们可以在我们项目路径下添加一下我们的私钥如&#xff1a; 首先确定我们ssh是正常启动的eval $(ssh-agent)我们可以…

AC/DC电源模块:提供高质量的电力转换解决方案

BOSHIDA AC/DC电源模块&#xff1a;提供高质量的电力转换解决方案 AC/DC电源模块是一种电力转换器件&#xff0c;可以将交流电转换为直流电。它通常用于各种电子设备和系统中&#xff0c;提供高质量的电力转换解决方案。 AC/DC电源模块具有许多优点。首先&#xff0c;它能够提…

让大模型变得更聪明:人工智能的未来发展之路

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

以JVM新特性看Java的进化之路:从Loom到Amber的技术篇章

引言&#xff1a; JVM的最新特性通过在效率、功能和易用性方面的创新&#xff0c;对Java的未来发展产生了深远的影响。以下是几个关键特性如何塑造了Java的未来&#xff1a; 正文&#xff1a; 轻量级并发 - 项目Loom&#xff1a; 项目Loom通过引入虚拟线程&#xff08;也被称为…

江苏职称申报大揭秘:你所不知道的那些细节

大家好&#xff01;今天我将带大家深入探索江苏职称申报的一些你可能从未关注过的细节。对于在江苏工作的工程类小伙伴们来说&#xff0c;这些信息或许能助你一臂之力&#xff0c;让你在职称申报的道路上更加顺畅。 我们要明确的是&#xff0c;江苏省的工程类职称申报主要有三种…

每日一题——只需一行Python秒杀:PAT乙级1009 说反话!但不能故步自封!(举一反三+思想解读+逐步优化)

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 我的写法 各部分功能分析&#xff1a; 综合时间复杂度 综合空间复杂度 总结 思路…

读人工智能时代与人类未来笔记15_改变人类经验

1. 认识世界的方式 1.1. 信仰 1.2. 理性 1.2.1. 理性不仅革新了科学&#xff0c;也改变了我们的社会生活、艺术和信仰 1.2.2. 在其浸染之下&#xff0c;封建等级制度瓦解了&#xff0c;而民主&#xff0c;即理性的人应该自治的理念崛起了 1.3. 人工智能 1.3.1. 这种转变将…

大数据开发面试题【Kafka篇】

83、介绍下Kafka&#xff0c;Kafka的作用?Kafka的组件?适用场景? kafka是一个高吞吐量、可扩展的分布式消息传递系统&#xff0c;在处理实时流式数据&#xff0c;并能够保证持久性和容错性 可用于数据管道、流分析和数据继承和关键任务应用&#xff08;发布/订阅模式&#…

Vue3+Ant design 实现Select下拉框一键全选/清空

最近在做后台管理系统项目的时候&#xff0c;产品增加了一个让人非常苦恼的需求&#xff0c;让在Select选择器中添加一键全选和清空的功能&#xff0c;刚开始听到的时候真是很懵&#xff0c;他又不让在外部增加按钮&#xff0c;其实如果说在外部增加按钮实现全选或者清空的话&a…

3、python安装-linux系统下

安装前置依赖软件&#xff0c;安装完成后&#xff0c;打开官网&#xff0c;下载linux系统下的python安装包&#xff1a; 选择最新的版本 点击最新版本&#xff0c;进入版本对应的界面&#xff0c; 选择第一个进行源码的编译&#xff0c;右键选择复制连接地址&#xff0c; 回到终…

HTML+CSS+JS(web前端大作业)~致敬鸟山明简略版

HTMLCSSJS【动漫网站】网页设计期末课程大作业 web前端开发技术 web课程设计 文章目录 一、网站题目 二、网站描述 三、网站介绍 四、网站效果 五、 网站代码 文章目录 一、 网站题目 动漫网站-鸟山明-龙珠超 二、 网站描述 页面分为页头、菜单导航栏&#xff08;最好可下拉&…

CDC 数据实时同步入湖的技术、架构和方案(截至2024年5月的现状调研)

近期&#xff0c;对 “实时摄取 CDC 数据同步到数据湖” 这一技术主题作了一系列深入的研究和验证&#xff0c;目前这部分工作已经告一段落&#xff0c;本文把截止目前&#xff08;2024年5月&#xff09;的研究结果和重要结论做一下梳理和汇总。为了能给出针对性的技术方案&…

ESP32-C6接入巴法云,Arduino方式

ESP32-C6接入巴法云&#xff0c;Arduino方式 第一、ESP32-C6开发环境搭建第一步&#xff1a;安装arduino IDE 软件第二步&#xff1a;安装esp32库第三&#xff1a;arduino 软件设置 第二&#xff1a;简单AP配网程序第一步&#xff1a;程序下载第二步&#xff1a;程序使用第三步…

电脑微信群发 500 1000人以上怎么群发,微信营销群发助手软件,本人亲测,增加十倍业绩!!!

今天给大家推荐一款我们目前在使用的电脑群发工具掘金小蜜&#xff0c;不仅可以无限多开&#xff0c;方便你同时管理多个账号&#xff0c;群发功能更是十分强大&#xff0c;轻松释放你的双手。 掘金小蜜&#xff08;只支持Win7及以上操作系统&#xff0c;没有推Mac版和手机客户…

[码蹄集新手训练营]MT1016-MT1020

目录 题号MT1016 宽度与对齐MT1017 左右对齐MT1018 输入宽度MT1020 %s格式符 题号 MT1016 宽度与对齐 #include<stdio.h> int main() { printf("%-5d %5d\n%-5d %5d\n%-5d %5d",455,455,-123,-123,987654,987654);return 0; }MT1017 左右对齐 #include<s…

Mac | macOs系统安装Monuty解决外接u盘ntfs读写问题

问题 mac电脑的macOs系统无法将文件读写入外接u盘或硬盘中&#xff1b; 解决方案 安装Monuty 官网&#xff1a;mounty官网 下载软件 安装其他配置 macbook:~ uwe$ brew install --cask macfuse macbook:~ uwe$ brew install gromgit/fuse/ntfs-3g-mac macbook:~ uwe$ brew…

机顶盒也可以跑量--上机指南

一、背景介绍 随着科技的进步和智能设备的普及&#xff0c;机顶盒已不再是单纯的电视接收器&#xff0c;而是逐渐演变成为家庭娱乐中心。越来越多的机顶盒支持各种应用、游戏和功能&#xff0c;使得用户可以在大屏幕上享受更多样化的内容。本指南将带你深入了解如何让你的机顶…

价格预言机领导者 Pyth 与 Eclipse 平台集成,为高频 DeFi 应用提供支持

本篇文章将对这一战略合作伙伴关系&#xff0c;以及 Pyth 网络在 Eclipse 生态系统中扮演的关键角色进行深入探讨。 目前&#xff0c;Pyth 价格数据已正式上线于 Eclipse 测试网。Eclipse 是首个结合了以太坊安全性、Solana 性能和 Celestia DA 的 Solana虚拟机(SVM) Layer2 方…

无线麦克风哪个牌子性价比高?揭秘领夹麦克风性价比最高品牌

随着自媒体行业的兴起&#xff0c;现在视频直播或者是个人Vlog都越来越受欢迎了&#xff0c;要想做好内容&#xff0c;除了画面之外&#xff0c;声音效果同样重要。而我们手机上自带的麦克风&#xff0c;容易受环境影响&#xff0c;特别是在户外或者拍摄距离较远时&#xff0c;…