Linux操作系统学习(线程同步)
文章目录
- 线程同步
- 条件变量
- 生产者与消费者模型
- 信号量
- 环形队列应用生产者消费者模型
线程同步
现实生活中我们经常会遇到同一个资源多个人都想使用的问题,例如游乐园过山车排队,玩完的游客还想再玩,最好的办法就是玩完的游客想再玩就去重新排队
线程同步其实就是一种等待机制,多个想要同时访问同一个对象的线程形成一个类似等待队列,等待前面的线程使用完毕后,下一个线程再使用。
线程同步的概念:
在保证数据安全的前提下(加锁保护),让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
为什么要线程同步:
互斥是保证线程的安全。但当一个线程访问了临界资源后,释放了它的锁,同时立刻参与到了锁的竞争中,如果它又拿到了锁。那么其他线程就会由于长时间得不到锁访问不了临界资源而造成线程饥饿问题。
同步能让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,使多线程协同高效完成某些事情
同步就是在保证数据安全的前提下,让线程按照某种特定的顺序来访问临界资源。
同步与互斥的关系:互斥是保证数据的安全,同步在互斥的前提下,来提高线程之间的效率。
程序没有安全性的问题,就没有必要使用同步
条件变量
条件变量是类型为pthread_cond_t的变量,是利用线程间共享的全局变量进行同步的一种机制,主要有两个动作:
线程对某个临界资源进行条件判断,为真则执行代码,为假则挂起等待,节省CPU资源避免空等(也可以反着来,主要是挂起等待节省资源)
其他线程在执行某些动作后使条件成立,唤醒等待的线程
它可以用来保证:在某个线程没有满足某种条件完成之前,其他线程只能挂起等待。
条件变量一般用到4个接口:
int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr);
功能:初始化条件变量
cv:要初始化的条件变量
cattr:设置条件变量属性,一般置NULL交给OS默认设置即可
返回值:成功返回0,失败返回错误码
int pthread_cond_destroy(pthread_cond_t *cond)
功能:释放申请的条件变量
返回值:成功返回0,失败返回错误码
注意:条件变量所占的空间没有被销毁(静态区)
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
功能:将调用此函数的线程在指定条件变量中挂起等待
cv:要在哪个条件变量下挂起等待
mutex:线程调用此函数时,会自动释放传入的锁
返回值:成功返回0,失败返回错误码
int pthread_cond_signal(pthread_cond_t *cv);
功能:唤醒等待中的线程
cv:唤醒在哪个条件变量下等待的线程
返回值:成功返回0,失败返回错误码
broadcast是一次唤醒指定条件变量下多个线程
下面用一段代码验证条件变量的用法
代码逻辑:一共申请6个线程,其中一个线程负责发布命令,另外5个线程负责工作
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mtx;
pthread_cond_t cond;//发布命令线程
void* master(void* args)
{std::string name = (char*)args;while(true){//唤醒在条件变量下等待的一个线程std::cout << "begin run:" << std::endl;pthread_cond_signal(&cond);sleep(1);}
}
//工作线程[5]
void* threadrun(void* args)
{int num = *(int*)args;delete (int*)args;while(true){pthread_cond_wait(&cond,&mtx);std::cout << "thread[" << num << "]running. . . " << std::endl;}
}
//每个线程再唤醒执行后经过while循环再次挂起等待int main()
{//初始化条件变量pthread_mutex_init(&mtx,nullptr);pthread_cond_init(&cond,nullptr);pthread_t tid[5];pthread_t boss;pthread_create(&boss,nullptr,master,(void*)"boss");for(size_t i = 0;i < 5;i++){int* num = new int(i);//用堆区变量去传递线程号pthread_create(tid+i,nullptr,threadrun,(void*)num);}//最后记得要等待线程以及释放锁和条件变量for(size_t i = 0;i < 5;i++)pthread_join(tid[i],nullptr);pthread_join(boss,nullptr);pthread_mutex_destroy(&mtx);pthread_cond_destroy(&cond);return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RjRtRlKO-1677869705609)(G:\Typora\图片保存\image-20221227014403285.png)]](https://img-blog.csdnimg.cn/17daf1aa47174b48ab473af1d6fd249c.png)
通过输出结果有如下分析:
-
条件变量内部一定有一个等待队列,哪个线程调用wait,哪个线程就挂起等待并进入等待队列,唤醒顺序就是等待的顺序
(线程执行的顺序是不一定的)
-
wait函数一定是释放锁的,否则线程调用该函数是抱着锁挂起等待的,其他线程就无法访问临界区了
-
signal函数唤醒的线程也一定会去争锁,争到才会继续执行;否则可能会出现一个带锁的线程访问临界区,和一个刚唤醒的线程继续访问临界区等方面的错误
-
在mutex已上锁的时候才能调用wait()
条件变量通常和互斥锁一起使用,互斥是保证线程的安全,条件变量防止互斥造成的饥饿问题
生产者与消费者模型
条件变量使用“通知—唤醒”模型,例如网购商家会发快递,我们只需要等待快递到了给我们发送提示短信;运用在多线程中最经典的就是生产者—消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理, 直接扔给阻塞队列, 消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出
上面的说法放到现实中解释就是,消费者—超市—供货商这样的模型,消费者购买商品不需要找供货商,只需要去超市即可,而供货商也不用管消费者,只需要给超市提供商品。
消费者购买商品的同时,供货商也在生产商品并向超市提供,若供货商出现问题,超市会有存品可以暂时共给消费者,并寻找新的供货商,此时超市就解决了消费者和供货商之间的耦合问题
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q1SfW0ln-1677869705610)(G:\Typora\图片保存\image-20221226022303881.png)]](https://img-blog.csdnimg.cn/e66fa00ad3ab4c419ea87d5061d569c9.png)
下面用代码验证一下模型
代码逻辑:
- 设计一个类,对普通队列、条件变量、互斥量进行封装
- Push对应生产者,利用条件变量对其限制,队列为满就挂起等待,待消费者消费数据后唤醒
- Pop对应消费者,利用条件变量对其限制,队列为空就挂起等待,待生产者生产数据后唤醒
/************************BlockQueue.hpp************************/#pragma once
#include <iostream>
#include <queue>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <cstdlib>namespace dd
{template<class T>
class BlockQueue
{
public://初始化BlockQueue():_capacity(10){pthread_cond_init(&_empty,nullptr);pthread_cond_init(&_full,nullptr);pthread_mutex_init(&_mtx,nullptr);}//释放互斥量、条件变量~BlockQueue(){pthread_cond_destroy(&_empty);pthread_cond_destroy(&_full);pthread_mutex_destroy(&_mtx);}//生产逻辑void Push(const T& key){Lockqueue();while(full())ProducterWait();_bq.push(key);UnlockQueue();WakeupConsumer();}//消费逻辑void Pop(T* key){Lockqueue();while(empty())ConsumerWit();*key = _bq.front();_bq.pop();UnlockQueue();WakeupProducter();}//队列为空则为真bool empty(){return _bq.empty();} //队列为满则为真,这里最大容量设置的是10bool full(){return _bq.size() == _capacity-1; //先判断 后push,所以要-1}//加锁void Lockqueue(){pthread_mutex_lock(&_mtx);}//解锁void UnlockQueue(){pthread_mutex_unlock(&_mtx);}//队列满时生产者挂起等待void ProducterWait(){pthread_cond_wait(&_full,&_mtx);}//生产者唤醒等待void WakeupProducter(){pthread_cond_signal(&_full);}//队列为空时消费者挂起等待void ConsumerWit(){pthread_cond_wait(&_empty,&_mtx);}//消费者唤醒等待void WakeupConsumer(){pthread_cond_signal(&_empty);}private:std::queue<T> _bq;int _capacity; //最大容量本示例设置的是10pthread_mutex_t _mtx;pthread_cond_t _empty;pthread_cond_t _full;
}; }/************************************************************************************************/
#include "BlockQueue.hpp"
using namespace dd;void* consumer(void* args)
{BlockQueue<int>* bq = (BlockQueue<int>*)args;while(true){sleep(1);int data = 0;bq->Pop(&data);std::cout << "消费了一个数据:"<< data << std::endl;}
}void* producter(void* args)
{BlockQueue<int>* bq = (BlockQueue<int>*)args;while(true){int data = rand()%20+1;std::cout << "生产了一个数据:" << data << std::endl;bq->Push(data);}
}int main()
{srand((long long)time(nullptr));BlockQueue<int>* bq = new BlockQueue<int>();pthread_t c,p;pthread_create(&c,nullptr,consumer,(void*)bq); pthread_create(&p,nullptr,producter,(void*)bq); pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-elm7NfMw-1677869705610)(G:\Typora\图片保存\image-20221228183625402.png)]](https://img-blog.csdnimg.cn/b0b590b037804cb19d987b8f1defb32d.png)
上述示例需要注意:
-
虚假唤醒问题
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrnfHmKV-1677869705610)(G:\Typora\图片保存\image-20221228184108194.png)]](https://img-blog.csdnimg.cn/5e7d8c8d67ce41c9949d19ffd2633d5b.png)
在wait时,必须把它放到while里,而不是if里,为了防止虚假唤醒
例如上述示例中再增加一个线程,1号线程是生产者,2、3号线程是消费者,当2号线程获取走最后一个数据后,3号线程也想获取,但发现生产队列为NULL于是就挂起等待,随后1号线程生产了一个数据便唤醒3号,但是2号又先把数据获取走了,3号就属于虚假唤醒
3号线程唤醒后获取竞争锁,竞争到了以后继续执行Pop,但是队列已经为NULL了,所以利用while再次判断可预防这种现象,这种现象常见于多核CPU多线程中。
上述示例代码,也可以将发送数据改为发送任务:
- 把发送数据改为发送加减乘除的任务
- 再申请一个类,负责确定任务完成任务的逻辑功能部分,同时也是阻塞队列的数据类型
- 生产者只负责确定要算的数和算法
- 消费者负责完成生产者发布的任务
综上,变更的部分有:新增的类、最后cpp执行部分
/************************BlockQueue.hpp************************/
#pragma once
#include <iostream>
#include <queue>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <cstdlib>namespace dd
{template<class T>
class BlockQueue
{
public:BlockQueue():_capacity(10){pthread_cond_init(&_empty,nullptr);pthread_cond_init(&_full,nullptr);pthread_mutex_init(&_mtx,nullptr);}~BlockQueue(){pthread_cond_destroy(&_empty);pthread_cond_destroy(&_full);pthread_mutex_destroy(&_mtx);}void Push(const T& key){Lockqueue();while(full())ProducterWait();_bq.push(key);UnlockQueue();WakeupConsumer();}void Pop(T* key){Lockqueue();while(empty())ConsumerWit();*key = _bq.front();_bq.pop();UnlockQueue();WakeupProducter();}bool empty(){return _bq.empty();} bool full(){return _bq.size() == _capacity-1; //先判断 后push,所以要-1}void Lockqueue(){pthread_mutex_lock(&_mtx);}void UnlockQueue(){pthread_mutex_unlock(&_mtx);}void ProducterWait(){pthread_cond_wait(&_full,&_mtx);}void WakeupProducter(){pthread_cond_signal(&_full);}void ConsumerWit(){pthread_cond_wait(&_empty,&_mtx);}void WakeupConsumer(){pthread_cond_signal(&_empty);}private:std::queue<T> _bq;int _capacity;pthread_mutex_t _mtx;pthread_cond_t _empty;pthread_cond_t _full;
}; }/************************Task.hpp************************/
#pragma once
#include <iostream>
#include <pthread.h>
namespace dd
{class Task
{
public:Task(){}Task(int x,int y,char op):_x(x),_y(y),_op(op){}int Run(){int ret = 0;switch(_op){case '+':ret = _x + _y;break;case '-':ret = _x - _y;break;case '*':ret = _x * _y;break;case '/':ret = _x / _y;break;case '%':ret = _x % _y;break;default:break;}//std::cout << _x << _op << _y << " = " << ret << std::endl;std::cout << pthread_self() << ": " << _x << _op << _y << " = " << ret << std::endl;}
private:int _x;int _y;char _op;};}/************************************************************************************************/
#include "BlockQueue.hpp"
#include "Task.hpp"
using namespace dd;void* consumer(void* args)
{BlockQueue<Task>* bq = (BlockQueue<Task>*)args;while(true){Task t;bq->Pop(&t);t.Run();}
}void* producter(void* args)
{BlockQueue<Task>* bq = (BlockQueue<Task>*)args;std::string ops = "+-*/%";while(true){int x = rand()%20+1;int y = rand()%20+1;char op = ops[rand()%5];Task t(x,y,op);bq->Push(t);sleep(1);}
}int main()
{srand((long long)time(nullptr));BlockQueue<Task>* bq = new BlockQueue<Task>();pthread_t c1,c2,c3,p;pthread_create(&c1,nullptr,consumer,(void*)bq); pthread_create(&p,nullptr,producter,(void*)bq); pthread_create(&c2,nullptr,consumer,(void*)bq); pthread_create(&c3,nullptr,consumer,(void*)bq); pthread_join(c1,nullptr);pthread_join(c2,nullptr);pthread_join(c3,nullptr);pthread_join(p,nullptr);return 0;
}
直接使用互斥量,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争,提高了程序效率。
信号量
信号量的本质是一把计数器,用来描述临界资源中资源数目的大小,达到无冲突的访问共享资源目的
(例如飞机售票,把票看成信号量,买票就是申请信号量,卖票就是释放信号量)
伪代码如下:

临界资源分成5个部分(count=5),count就被称作信号量
count–,一个执行流占有临界资源一部分的操作叫做P操作
count++,一个执行流结束使用临界资源的一部分叫做V操作
count == 0,表示没有资源可以分配,此时的线程或进程就会被挂起等待(内部数据结构会有类似等待队列的结构)
但信号量也属于临界资源,所以V、P操作都是原子性的
- 信号变量的函数接口
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
-
作用:初始化信号量
-
sem:要初始化的信号量,同互斥量、条件变量一样,要创建sem_t类型的 变量
pshared:0表示线程间共享,大于0表示进程间共享value:信号量初始值,信号量个数
-
返回值:成功返回0,失败返回-1,并设置errno来表示错误
#include <semaphore.h>int sem_destroy(sem_t *sem);
- 作用:销毁定义的信号量
- sem:要销毁的信号量
- 返回值:成功返回0,失败返回-1,并设置errno来表示错误
#include <semaphore.h>int sem_wait(sem_t *sem);
- 作用:等待信号量,将信号量的值减1,如果信号量为0,阻塞等待。V( )操作
- sem:要等待的信号量
- 返回值:成功返回0,失败返回-1,并设置errno来表示错误
#include <semaphore.h>int sem_post(sem_t *sem);
- 作用:表示资源使用完毕,将信号量做加1操作。P( )操作
- sem:要发布的信号量
- 返回值:成功返回0,失败返回-1,并设置errno来表示错误
环形队列应用生产者消费者模型
基本原理:
- 生产者和消费者在一开始时是指向同一位置,代表队列为空,应该让消费者等待,生产者工作
- 生产者和消费者当之后所在同一位置,代表队列为满,应该让消费者工作,生产者等待
- 其余时候,生产者和消费者 一定不 指向同一位置

注意事项:
- 在不是同一位置时,生产者必须在消费者前面
- 在同一位置时,为空让生产者先走,为满让消费者先走,但是消费者不可以套圈
- 消费者最关心队列中的数据,因此可以定义一个信号量关心队列已有数据个数
- 生产最关心队列的空位置,因此可以定义一个信号量关心队列的空位置
- 不能让它们同时执行,但是可以并发执行
代码示例:
/**********************************ring_queue.hpp***************************************/
#pragma once#include <iostream>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#include <vector>namespace dd
{template<class T>
class Ring_queue
{
public://初始化Ring_queue():_cap(10),_c_step(0),_p_step(0){sem_init(&_blank_sem,0,10); //位置信号量设置初始值为10sem_init(&_data_sem,0,0); //数据信号量设置初始值为0_rq.reserve(10);}//释放信号量~Ring_queue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}//生产void Push(const T& key){//申请数据消费信号量,放入数据,释放位置信号量sem_wait(&_blank_sem);_rq[_p_step] = key;sem_post(&_data_sem);//更新位置_p_step++;_p_step %= _cap;}//消费void Pop(T* key){//申请位置信号量,取出数据,释放数据消费信号量sem_wait(&_data_sem);*key = _rq[_c_step];sem_post(&_blank_sem);//更新位置_c_step++;_c_step %= _cap;}private:int _cap; //总容量std::vector<T> _rq; //队列sem_t _blank_sem; //位置信号量sem_t _data_sem; //数据信号量int _c_step; //消费者位置(下标)int _p_step; //生产者位置(下标)}; }/*************************************************************************/
#include "ring_queue.hpp"
#include <time.h>
using namespace dd;void* consumer(void* args)
{Ring_queue<int>* rq = (Ring_queue<int>*)args;while(true){int data = rand()%20 + 1;rq->Push(data);std::cout << "生产数据:" << data << std::endl;}}void* producter(void* args)
{Ring_queue<int>* rq = (Ring_queue<int>*)args;while(true){sleep(1);int data;rq->Pop(&data);std::cout << "消费数据:" << data << std::endl;}
}int main()
{srand((long long)time(nullptr));pthread_t c,p;Ring_queue<int>* rq = new Ring_queue<int>();pthread_create(&c,nullptr,consumer,(void*)rq);pthread_create(&p,nullptr,producter,(void*)rq);pthread_join(c,nullptr);pthread_join(p,nullptr);return 0;
}
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iqsOYh0J-1677869705611)(G:\Typora\图片保存\image-20230205201654712.png)]](https://img-blog.csdnimg.cn/95c0656bb3af4c59871843430b4bdc28.png)
-
多生产多消费的区别
void Push(const T& key){sem_wait(&_blank_sem);pthread_mutex_lock(&p_mtx_);_rq[_p_step] = key; _p_step++;_p_step %= _cap;pthread_mutex_unlock(&p_mtx_);sem_post(&_data_sem);}void Pop(T* key){sem_wait(&_data_sem);pthread_mutex_lock(&c_mtx_);*key = _rq[_c_step]; _c_step++;_c_step %= _cap;pthread_mutex_unlock(&c_mtx_);sem_post(&_blank_sem);}-
sem_wait是原子性的,但多生产多消费中的队列和下标是临界资源,它们不是原子的,所以需要加锁
-
另外先进行信号量申请相对效率高,因为无论是生产还是消费本质是它们的信号量不为0,而不是先拿到锁
最终,互斥锁加在信号量申请之后,避免争到锁但没有信号量的情况
-

相关文章:
Linux操作系统学习(线程同步)
文章目录线程同步条件变量生产者与消费者模型信号量环形队列应用生产者消费者模型线程同步 现实生活中我们经常会遇到同一个资源多个人都想使用的问题,例如游乐园过山车排队,玩完的游客还想再玩,最好的办法就是玩完的游客想再玩就去重新排…...
了解动态规划算法:原理、实现和优化指南
动态规划 详细介绍例子斐波那契数列最长回文子串优化指南优化思路斐波那契数列优化最长回文子串优化详细介绍 动态规划(Dynamic Programming,简称 DP)是一种通过将原问题拆分成子问题并分别求解这些子问题来解决复杂问题的算法思想。 它通常用于求解优化问题,它的核心思想…...
《NFL橄榄球》:明尼苏达维京人·橄榄1号位
明尼苏达维京人(英语:Minnesota Vikings)是一支职业美式足球球队,位于明尼苏达州的明尼阿波利斯。他们现时在国家橄榄球联合会北区参与国家美式足球联盟比赛。该球队本为美国美式足球联盟(AFL)的球队。但是…...
sheng的学习笔记-Actuator健康监控
前言在微服务系统里,对微服务程序的运行状况的跟踪和监控是必不可少的;例如GPE,TelegrafinfluxDB都提供了微服务体系监控的方案, ZIPKIN, Skywalking都提供了微服务云体系的APM的方案; 这些解决方案功能全面…...
初次使用ESP32-CAM记录
模块的配置和图片 摄像头:8225N V2.0 171026 模块esp-32s 参考资料:https://docs.ai-thinker.com/esp32 配置环境 参考:https://blog.csdn.net/weixin_43794311/article/details/128622558 简单使用需要注意的地方 基本的环境配置和串口…...
华为OD机试真题Python实现【最长连续交替方波信号】真题+解题思路+代码(20222023)
最长连续交替方波信号 题目 输入一串方波信号,求取最长的完全连续交替方波信号,并将其输出, 如果有相同长度的交替方波信号,输出任一即可,方波信号高位用1标识,低位用0标识 如图: 说明: 一个完整的信号一定以0开始然后以0结尾, 即 010 是一个完整的信号,但101,101…...
【操作系统原理实验】页面替换策略模拟实现
选择一种高级语言如C/C等,编写一个页面替换算法的模拟实现程序。1) 设计内存管理相关数据结构;2) 随机生成一个页面请求序列;3) 设置内存管理模拟的关键参数;4) 实现该页面置换算法;5) 模拟实现给定配置请求序列的换页…...
Java中解析XML文件
1 在Java中解析XML文件共有四种方式 A、DOM方式解析XML数据 树结构,有助于更好地理解、掌握,代码易于编写,在解析过程中树结构是保存在内存中,方便修改 B、SAX方式解析 采用事件驱动模式,对内存消耗比较小࿰…...
二点回调测买 源码
如图所示,两点回调测买点的效果图,这是我们常见的一种预测买点计算方法。 现将源码公布如下: DRAWKLINE(H,O,L,C); N:13; A1:REF(HIGH,N)HHV(HIGH,2*N1); B1:FILTER(A1,N); C1:BACKSET(B1,N1); D1:FILTER(C1,N); A2:REF(LOW,N)LLV(LOW,2*N1…...
钉钉端H5开发调试怎么搞
H5开发本地调试教程 作为一名前端开发,大家平时工作中或多或少都有接触或需要开发H5页面的场景,在开发过程中,如何像PC端页面一样有有丝滑的体验呢? 不同的情况需要在不同的端调试更方便有效: 1. 在画UI的时候,更适合在PC端调试,更改代码或者直接在浏览器调试,都是实…...
Mysql Server原理简介
Mysql客户端包括JDBC、 Navicat、sqlyog,只是为了和mysql server建立连接,向mysql server提交sql语句。mysql server组件第一部分叫连接器主要承担的功能叫管理连接和验证权限,每次在进行数据库访问的时候,必然要输入用户名和密码…...
23种设计模式-外观模式
外观模式是一种结构型设计模式,它提供了一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,使得客户端可以更加方便地访问子系统的功能。在这篇博客中,我们将讨论如何使用Java实现外观模式,并通…...
使用 Vulkan VkImage 作为 CUDA cuArray
使用 Vulkan VkImage 作为 CUDA cuArray【问题标题】:Use Vulkan VkImage as a CUDA cuArray使用 Vulkan VkImage 作为 CUDA cuArray【发布时间】:2019-08-20 20:01:10【问题描述】:将 Vulkan VkImage 用作 CUDA cuArray 的正确方法是什么&am…...
电商API接口-电商OMS不可或缺的一块 调用代码展示
电商后台管理系统关键的一环就是实现电商平台数据的抓取,以及上下架商品、订单修改等功能的调用。这里就需要调用电商API接口。接入电商API接口后再根据自我的需求进行功能再开发,实现业务上的数字化管理。其中订单管理模板上需要用到如下API:seller_ord…...
Solaris ZFS文件系统rpool扩容
ZFS文件系统简介 Solaris10默认的文件系统是ufs(Unix Filesystem),当然也可以选装zfs;Solaris11默认的文件系统是zfs(Zettabyte Filesystem)。 ZFS文件系统的英文名称为Zettabyte File System,也叫动态文件…...
模式识别 —— 第二章 参数估计
模式识别 —— 第二章 参数估计 文章目录模式识别 —— 第二章 参数估计最大似然估计(MLE)最大后验概率估计(MAP)贝叶斯估计最大似然估计(MLE) 在语言上: 似然(likelihood…...
判断4位回文数-课后程序(Python程序开发案例教程-黑马程序员编著-第3章-课后作业)
实例1:判断4位回文数 所谓回文数,就是各位数字从高位到低位正序排列和从低位到高位逆序排列都是同一数值的数,例如,数字1221按正序和逆序排列都为1221,因此1221就是一个回文数;而1234的各位按倒序排列是43…...
【NLP】Word2Vec 介绍
Word2Vec 是一种非常流行的自然语言处理技术,它将每个单词表示为高维向量,并且通过向量之间的相似度来表示单词之间的语义关系。 1 One-Hot 编码🍂 在自然语言处理任务中,我们需要将文本转换为计算机可以理解的形式,即…...
3月6日,30秒知全网,精选7个热点
///石家庄地铁:在指定店铺购物金额不限 就可免费乘地铁 乘客只要在指定商铺或地铁站内36524便利店购物,便能得到一张当日乘车券,可免费乘坐地铁一次,不限里程 ///神州泰岳:公司语音机器人等产品能够进行多轮问答 公司…...
Python笔记 -- 字典
文章目录1、概述2、增删改查3、遍历3.1、遍历所有键值对3.2、分别遍历键和值4、嵌套4.1、字典列表4.2、在字典中储存列表4.3、在字典中储存字典1、概述 字典是一系列键值对,可将任何Python对象作为字典中的值 字典和列表容易混淆,列表也可用{} 字典是一…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1PsOO5fC-1677869705608)(G:\Typora\图片保存\image-20221227012439771.png)]](https://img-blog.csdnimg.cn/82b61641fe1f40de84fa7a2657eeabd1.png)