【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.pswp.cn/bicheng/16738.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/16738.shtml
英文地址,請注明出處:http://en.pswp.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)我們可以…

前端下載功能

1.創建a標簽并點擊 let a document.createElement(a); a.href url; a.download name.xlsx; a.click(); 2.如果只是替換了當前路由并預覽的話&#xff0c;可以強制瀏覽器下載 var pdfUrl "" // 替換為你的PDF文件鏈接 fetch(pdfUrl).then(response > respons…

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;也被稱為…

飛槳圖像識別套件PaddleClas安裝

安裝驗證 使用以下命令可以驗證 PaddlePaddle 是否安裝成功。 import paddle paddle.utils.run_check() 查看 PaddlePaddle 版本的命令如下&#xff1a; python -c "import paddle; print(paddle.__version__)" 安裝 PaddleClas 及其 Python 依賴庫 [建議] 直接…

江蘇職稱申報大揭秘:你所不知道的那些細節

大家好&#xff01;今天我將帶大家深入探索江蘇職稱申報的一些你可能從未關注過的細節。對于在江蘇工作的工程類小伙伴們來說&#xff0c;這些信息或許能助你一臂之力&#xff0c;讓你在職稱申報的道路上更加順暢。 我們要明確的是&#xff0c;江蘇省的工程類職稱申報主要有三種…

每日一題——只需一行Python秒殺:PAT乙級1009 說反話!但不能故步自封!(舉一反三+思想解讀+逐步優化)

一個認為一切根源都是“自己不夠強”的INTJ 個人主頁&#xff1a;用哲學編程-CSDN博客專欄&#xff1a;每日一題——舉一反三Python編程學習Python內置函數 Python-3.12.0文檔解讀 目錄 我的寫法 各部分功能分析&#xff1a; 綜合時間復雜度 綜合空間復雜度 總結 思路…

Oracle可視化性能圖表之 “CPU 內存 網絡等數據性能分析”

Oracle 性能視圖查看系統CPU 內存 磁盤 存儲等性能指標主要保存在 V$SYSMETRIC_HISTORY及DBA_HIST_SYSMETRIC_HISTORY 相關視圖上。 此次我們以網絡帶寬傳輸速率&#xff1a; 例如&#xff1a;目標是在Data Guard環境中盡可能快地傳輸和應用重做。為了實現這一點&#xff0c;…

讀人工智能時代與人類未來筆記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;發布/訂閱模式&#…

Kafka 集群部署(CentOS 單機模擬版)

0.前置說明 由于我們手里只有一臺Linux機器&#xff0c;所以我們實現的是簡單的單機模擬的集群部署&#xff0c;通過修改配置文件&#xff0c;啟動 3 個 kafka 時用到 3 個不同的端口&#xff08;9091&#xff0c;9092&#xff0c;9093&#xff09;。 1.安裝Java11 切換到你…

鏈接庫文件體積優化工具篇:bloaty

筆者之前參與過一個嵌入式智能手表項目&#xff0c;曾經碰到過這樣一個問題&#xff1a;手表的flash大小只有2M&#xff0c;這意味著只能在上面燒錄2M大小的代碼。隨著開發不斷進行&#xff0c;代碼越寫越多&#xff0c;編譯出來的bin也越來越大。最后bin大小超過了2M, 就沒法燒…

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;的研究結果和重要結論做一下梳理和匯總。為了能給出針對性的技術方案&…

Redis和MySQL的結合方式

Redis和MySQL的結合方式可以多樣化&#xff0c;以滿足不同的應用需求。以下是幾種常見的結合方式&#xff0c;以及它們的特點和適用場景&#xff1a; 緩存數據庫查詢結果&#xff1a; 應用程序首先嘗試從Redis中查詢數據。如果Redis中沒有所需數據&#xff0c;則從MySQL數據庫中…

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

ESP32-C6接入巴法云&#xff0c;Arduino方式 第一、ESP32-C6開發環境搭建第一步&#xff1a;安裝arduino IDE 軟件第二步&#xff1a;安裝esp32庫第三&#xff1a;arduino 軟件設置 第二&#xff1a;簡單AP配網程序第一步&#xff1a;程序下載第二步&#xff1a;程序使用第三步…