Linux_线程同步生产者消费者模型
同步的相关概念
- 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
- 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。
同步的意义:
互斥保证安全性,安全不一定合理或高效!!同步主要是在保证安全的前提下,让系统变得更加合理和高效!
条件变量
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。条件变量是一个用来进行线程同步的特性,内部要维护线程队列。
条件变量函数
pthread_cond_init函数
// 初始化局部的条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t
*restrict attr);// 使用宏值初始化(全局),类比pthread_mutex_init
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
cond是指向条件变量对象的指针,该对象将被初始化。attr是指向条件变量属性的指针,用于指定条件变量的属性。如果此参数为NULL,则使用默认属性。在大多数应用中,通常传递NULL。函数成功时返回
0;出错时返回错误码。
pthread_cond_wait函数
// 使线程在条件变量上等待,直到该条件变量被另一个线程的信号
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict
mutex);
cond是指向条件变量对象的指针。mutex是指向互斥锁对象的指针,该互斥锁必须在调用pthread_cond_wait之前被当前线程持有(即锁定状态)。
pthread_cond_signal函数
// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast函数
// 唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);
一个Demo样例
// 初始化全局的锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void* active(void* args)
{std::string name = static_cast<const char*>(args);while(true){pthread_mutex_lock(&mutex);// 让每一个线程去条件变量那里去等待pthread_cond_wait(&cond, &mutex); printf("%s is active!\n", name.c_str());pthread_mutex_unlock(&mutex);}
}
int main()
{pthread_t tid1, tid2, tid3;pthread_create(&tid1, nullptr, active, (void*)"thread-1");pthread_create(&tid2, nullptr, active, (void*)"thread-2");pthread_create(&tid3, nullptr, active, (void*)"thread-3");while(true){printf("main wakeup thread...\n");// 我唤醒一个线程,一个线程去做相应的工作pthread_cond_signal(&cond);sleep(1);}pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);return 0;
}

将上面的pthread_cond_signal换成下面这句代码,线程就会全部被唤醒。
pthread_cond_broadcast(&cond);

生产者消费者模型
什么是生产者消费者模型
生产者消费者模型(Producer-Consumer Model)是一种经典的多线程同步问题,用于描述两个或多个线程之间共享有限资源的场景。在这个模型中,生产者负责生成数据并将其放入缓冲区(Buffer),消费者则从缓冲区中取出数据并消费。生产者和消费者通过缓冲区进行通信,但它们的执行速度可能不同,因此需要通过同步机制来协调它们的行为。
为什么要使用生产者消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
生产者消费者模型的优点:解耦、支持并发、支持忙闲不均。
1分钟记住生产者消费者模型
生产者消费者模型可简单理解为321原则:
- 三种关系(生产和生产(互斥),消费和消费(互斥),生产和消费(互斥&&同步))
- 两种角色(生产线程,消费线程)
- 一个缓冲区(一段内存空间,如阻塞队列)

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

Blocking Queue的Demo代码
设计思路:
(1)定义阻塞队列
阻塞队列是生产者和消费者之间的通信桥梁。它需要支持以下操作:
- 入队(put):生产者调用,将数据放入队列。如果队列已满,则阻塞生产者线程,直到有空间可用。
- 出队(take):消费者调用,从队列中取出数据。如果队列为空,则阻塞消费者线程,直到有数据可用。
(2)生产者逻辑
生产者负责生成数据并将其放入阻塞队列。
生成数据。
调用阻塞队列的
put方法将数据放入队列。如果队列已满,生产者线程会被阻塞,直到有空间可用。
(3)消费者逻辑
消费者负责从阻塞队列中取出数据并消费。
调用阻塞队列的
take方法从队列中取出数据。如果队列为空,消费者线程会被阻塞,直到有数据可用。
消费数据
// BlockQueue.hpp
#pragma once
#include <iostream>
#include <queue>static const int gcap = 10;
template <typename T>
class BlockQueue
{
private:bool IsFull() { return _q.size() == _cap; }bool IsEmpty() { return _q.empty(); }
public:BlockQueue(int cap = gcap): _cap(cap), _cwait_num(0), _pwait_num(0){// 基于RAII设计初始化和销毁pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_productor_cond, nullptr);pthread_cond_init(&_consumer_cond, nullptr);}void Put(const T &in) // 生产者{pthread_mutex_lock(&_mutex);// 为了防止伪唤醒,我们这里使用while进行判断// 伪唤醒是指将线程唤醒之后,线程没有资源可以访问,然后继续去等待while (IsFull()){_pwait_num++; // 若空间满了,就让等待的生产者数量++// 若满了,则生产者不能生产,必须在条件变量下等待// 临界区里面抱着锁等待是不被允许的!!!所以pthread_cond_wait// 被调用的时候:除了指明自己在那个条件变量下等待,还会释放自己抱着的锁pthread_cond_wait(&_productor_cond, &_mutex);// 返回时,线程被唤醒 && 重新申请并持有锁(它会在临界区内醒来!)// 才能继续往下运行_pwait_num--; // 生产者被唤醒,数量--}// while(IsFull())条件不满足 || 线程被唤醒, 退出循环_q.push(in);// 肯定有数据,唤醒消费者if (_cwait_num)pthread_cond_signal(&_consumer_cond);pthread_mutex_unlock(&_mutex);}void Take(T *out) // 消费者{pthread_mutex_lock(&_mutex);while (IsEmpty()){_cwait_num++; // 若数据空了,就让等待的消费者数量++pthread_cond_wait(&_consumer_cond, &_mutex);_cwait_num--; // 消费者被唤醒,数量--}*out = _q.front();_q.pop();// 肯定有空间,唤醒生产者if (_pwait_num)pthread_cond_signal(&_productor_cond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_productor_cond);pthread_cond_destroy(&_consumer_cond);}private:std::queue<T> _q; // 使用普通队列作为保存数据的容器int _cap; // bq最大容量pthread_mutex_t _mutex; // 互斥pthread_cond_t _productor_cond; // 生产者条件变量pthread_cond_t _consumer_cond; // 消费者条件变量int _cwait_num; // 在条件变量下等待的消费者数量int _pwait_num; // 在条件变量下等待的生产者数量
};
// Main.cc
#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>void* Consumer(void* args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);while(true){sleep(2);int data;// 1.从bq中拿到数据bq->Take(&data);// 2.做各种处理printf("Consumer, 消费了一个数据: %d\n", data);}
}
void* Productor(void* args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);// 1.从外部获取数据int data = 10;while(true){// 2.生产到bq中printf("Productor, 生产了一个数据: %d\n", data);bq->Put(data);data++;}
}
int main()
{BlockQueue<int> *bq = new BlockQueue<int>(4);// 共享资源// 单生产,单消费pthread_t c, p;// 最后一个参数传递bq,保证同一个线程看到同一个队列pthread_create(&c, nullptr, Consumer, bq);pthread_create(&p, nullptr, Productor, bq);pthread_join(c, nullptr);pthread_join(p, nullptr);delete bq;return 0;
}
让消费者睡眠2秒,生产者正常运行

让生产者睡眠2秒,消费者正常运行

ps. 上面代码是单生产单消费模式的,若想改为多生产多消费的,直接添加线程就可以。因为在生产者消费者模型的321原则中,21已经有了,3中的其中一种关系(生产和消费)已经有了,只需要补充完剩下的两种就可以。而我们设计的生产者与生产者或者消费者与消费者之间本身就只有一把锁,所以它们天然的就存在互斥关系!
基于环形队列的生产者消费者模型
认识POSIX信号量以及接口
信号量本身就是一个计数器,它用来描述你所申请资源数目的多少。
信号量原理:一元信号量(或二元信号量)主要用于实现资源的互斥访问。当一个线程(或进程)获取了信号量(将其值从1减为0),它便获得了对某个共享资源的独占访问权,其他试图获取该信号量的线程将被阻塞,直到信号量被释放(其值从0加回1)。
信号量对公共资源使用时可以整体使用,也可以不整体使用。整体使用就是把整个资源看作一份。当二元信号量的值为1时,表示资源未被占用,线程可以获取信号量并进入临界区;当信号量的值为0时,表示资源已被占用,其他线程必须等待。(如上面的阻塞队列)。
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。
信号量的两个重要操作
1. P操作(Wait操作)
P操作(也称为
sem_wait)用于等待信号量,其目的是减少信号量的值,并根据信号量的当前值决定是否阻塞当前线程或进程。2. V操作(Post操作)
V操作(也称为
sem_post)用于释放信号量,其目的是增加信号量的值,并可能唤醒等待该信号量的线程或进程。
sem_init函数
// 初始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数
- sem:指向要初始化的信号量对象的指针。
- pshared:控制信号量的作用域。如果pshared的值非0,则信号量在进程间共享;如果为0,则信号量仅在调用进程内的线程间共享。
- value:信号量的初始值。这个值必须是非负的。
返回值
成功时,sem_init 返回0。失败时,返回-1,并设置errno以指示错误
sem_destroy函数
// 销毁信号量
int sem_destroy(sem_t *sem);
参数
- sem:指向要销毁的信号量对象的指针。
返回值
- 成功时,
sem_destroy返回 0。失败时,返回 -1,并设置 errno 以指示错误。
sem_wait函数
// 等待信号量
int sem_wait(sem_t *sem); //P()
参数
- sem:指向要操作的信号量对象的指针。
返回值
- 成功时返回0。失败时返回-1,并设置errno以指示错误。
该函数用于信号量的P操作,从信号量的值中减去1,如果信号量的值变为0,则当前线程将被阻塞,直到信号量的值被其他线程通过 sem_post 增加。
sem_post函数
// 发布信号量
int sem_post(sem_t *sem);//V()
参数
- sem:指向要操作的信号量的指针。
返回值
- 成功时,sem_post返回0。
- 失败时,返回-1,并设置errno以指示错误。可能的错误包括EINVAL,表示传入的信号量不是一个有效的信号量。
该函数用于信号量的V操作,将信号量的值增加1;唤醒等待线程:如果有线程因为调用sem_wait函数而在该信号量上等待(即信号量的值为0且线程被阻塞),那么sem_post函数可能会唤醒其中一个等待的线程。具体唤醒哪个线程取决于操作系统的线程调度策略。
环形队列的Demo代码
这里环形队列我们使用数组模拟,用模运算来模拟环状特性。
设计思路:
1. 环形队列的结构
环形队列是一个固定大小的数组,通过两个指针(
head和tail)来表示队列的头部和尾部。队列的头部用于消费者读取数据,队列的尾部用于生产者写入数据。2. 生产者和消费者线程
2.1 生产者线程
使用信号量P(sem_wait函数)操作进行空间的预定,若没有空间则进行等待
加锁
写入数据:将数据写入
tail指向的位置。更新
tail:将tail向前移动一位:tail = (tail + 1) % size。解锁
释放一个数据,即消费者有新数据可用
2.2 消费者线程
使用信号量V(sem_post函数)操作进行数据的预定,若没有数据则进行等待
加锁
读取数据:从
head指向的位置读取数据。更新
head:将head向前移动一位:head = (head + 1) % size。解锁
释放一个空间,即生产者有空闲位置可用
3. 同步机制
为了确保生产者和消费者之间的同步,需要使用互斥锁(
mutex)和信号量(semaphore)来控制对共享资源的访问。
互斥锁:用于保护生产者线程和消费者线程,确保同一时间只有一个线程可以修改队列。
信号量:
sem_wait:表示等待信号量,从信号量的值中减去1
sem_post:表示发布信号量,从信号量的值中加1, 并唤醒等待的信号量
// Sem.hpp
#pragma once
#include <semaphore.h>int defaultvalue = 1;
class Sem
{
public:Sem(int value = defaultvalue){int n = ::sem_init(&_sem, 0, value);(void)n;}void P(){int n = ::sem_wait(&_sem);(void)n;}void V(){int n = ::sem_post(&_sem);(void)n;}~Sem(){int n = ::sem_destroy(&_sem);(void)n;}private:sem_t _sem; // 信号量int _init_value;
};
// RingBuffer.hpp
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include "Sem.hpp"template <typename T>
class RingBuffer
{
public:RingBuffer(int size): _ring(size), // 给vector初始化一个大小_size(size),_tail(0), // 生产者的默认位置_head(0), // 消费者的默认位置_datasem(0), // 默认数据为0_spacesem(size) // 默认空间为满{int n = ::pthread_mutex_init(&_p_lock, nullptr);(void)n;int m = ::pthread_mutex_init(&_c_lock, nullptr);(void)m;}// 生产者void Equeue(const T &in){_spacesem.P(); // 预定一个空间// 上锁pthread_mutex_lock(&_p_lock);_ring[_tail] = in; // 生产_tail++;_tail %= _size; // 维持环形特性pthread_mutex_unlock(&_p_lock);_datasem.V(); // 释放一个数据}// 消费者void Pop(T *out){_datasem.P(); // 预定一个数据// 上锁pthread_mutex_lock(&_c_lock);*out = _ring[_head]; // 取出数据_head++;_head %= _size;pthread_mutex_unlock(&_c_lock);_spacesem.V(); // 释放一个空间}~RingBuffer(){int n = ::pthread_mutex_destroy(&_p_lock);(void)n;int m = ::pthread_mutex_destroy(&_c_lock);(void)m;}private:std::vector<T> _ring; // 环, 临界资源int _size; // 总容量int _tail; // 生产者位置int _head; // 消费位置Sem _datasem; // 数据信号量Sem _spacesem; // 空间信号量pthread_mutex_t _p_lock; // 生产者锁pthread_mutex_t _c_lock; // 消费者锁
};
// Main.cc
#include "RingBuffer.hpp"
#include <string>
#include <unistd.h>// 生产
void *Consumer(void *args)
{RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);int data = 0;while (true){// sleep(1);ring_buffer->Equeue(data);std::cout << "生产了一个数据: " << data << std::endl;data++;}return nullptr;
}
// 消费
void *Productor(void *args)
{RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);while (true){sleep(2);int data;ring_buffer->Pop(&data);std::cout << "消费了一个数据: " << data << std::endl;}return nullptr;
}int main()
{RingBuffer<int> *ring_buffer = new RingBuffer<int>(5); // 共享资源 -> 临界资源pthread_t c1, p1, c2, c3, p2;pthread_create(&c1, nullptr, Consumer, ring_buffer);pthread_create(&c2, nullptr, Consumer, ring_buffer);pthread_create(&c3, nullptr, Consumer, ring_buffer);pthread_create(&p1, nullptr, Productor, ring_buffer);pthread_create(&p2, nullptr, Productor, ring_buffer);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(c3, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);delete ring_buffer;return 0;
}
相关细节:
1.为了完成多生产和多消费的模型,消费者和生产者的同步和互斥问题我们的环形队列中已经解决。除此之外,我们还需要维护生产者和生产者 &&消费者和消费者之间的互斥关系。由于环形队列的下标也是属于临界资源的,如果不维持关系内部的互斥关系,是一定会破坏环形队列结构的。所以势必要引入生产者互斥锁和消费者互斥锁。
2.先加锁 or 先申请信号量
一定是先申请信号量,在加锁。先申请信号量,会让所有线程先瓜分好信号量,在其它线程进行等待锁的时候,此时资源已经申请好了,可以直接进入临界区;如果先加锁,在申请信号量,所有线程都会在锁上等待,等待完成之后在去申请信号量,这样解决问题的实际效率会变低。
3.使用信号量实现生产者消费者模型,对资源进行使用,申请时,为什么不判断一下条件是否满足?
信号量本身就是判断条件! 信号量:是一个计数器,是资源的预订机制。预订:可以理解为在外部,可以不判断资源是否满足,就可以知道内部资源的情况!

相关文章:
Linux_线程同步生产者消费者模型
同步的相关概念 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。 同步的…...
Origami Agents:通过AI驱动的研究工具提升B2B销售效率
在当今竞争激烈的商业环境中,B2B销售团队面临着巨大的挑战,如何高效地发现潜在客户并进行精准的外展活动成为关键。Origami Agents通过其创新的AI驱动研究工具,正在彻底改变这一过程。本文将深入探讨Origami Agents的产品特性、技术架构以及其快速增长背后的成功因素。 一、…...
linux的/proc 和 /sys目录差异
/proc 和 /sys 都是Linux系统中用于提供系统信息和进行系统配置的虚拟文件系统,但它们的原理并不完全一样,以下是具体分析: 目的与功能 /proc :主要用于提供系统进程相关信息以及内核运行时的一些参数等,可让用户和程…...
AIGC时代的Vue或React前端开发
在AIGC(人工智能生成内容)时代,Vue开发正经历着深刻的变革。以下是对AIGC时代Vue开发的详细分析: 一、AIGC技术对Vue开发的影响 代码生成与自动化 AIGC技术使得开发者能够借助智能工具快速生成和优化Vue代码。例如,通…...
代码随想录算法训练营第三十九天-动态规划-337. 打家劫舍 III
老师讲这是树形dp的入门题目解题思路是以二叉树的遍历(递归三部曲)再结合动规五部曲dp数组如何定义:只需要定义一个二个元素的数组,dp[0]与dp[1] dp[0]表示不偷当前节点的最大价值dp[1]表示偷当前节点后的最大价值这样可以把每个节…...
Java线程认识和Object的一些方法
专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162 本文目标: 要对Java线程有整体了解,深入认识到里面的一些方法和Object对象方法的区别。认识到Java对象的ObjectMonitor,这有助于后面的Synchron…...
【算法应用】基于A*-蚁群算法求解无人机城市多任务点配送路径问题
目录 1.A星算法原理2.蚁群算法原理3.结果展示4.代码获取 1.A星算法原理 A*算法是一种基于图搜索的智能启发式算法,它具有高稳定性和高节点搜索效率。主要原理为:以起点作为初始节点,将其加入开放列表。从开放列表中选择具有最小总代价值 f (…...
电梯系统的UML文档14
对于 HallButtonControl,我们有二个状态: "门厅灯开 " 和 " 门厅灯关"。 从给出的初始信息,初始的状态应该是"门厅灯关"。行为定义: " 当 HallCall[f,d]是真,则指令 HallLight[f&…...
一种用于低成本水质监测的软传感器开源方法:以硝酸盐(NO3⁻)浓度为例
论文标题 A Soft Sensor Open-Source Methodology for Inexpensive Monitoring of Water Quality: A Case Study of NO3− Concentrations 作者信息 Antonio Jess Chaves, ITIS Software, University of Mlaga, 29071 Mlaga, Spain Cristian Martn, ITIS Software, Universi…...
[250130] VirtualBox 7.1.6 维护版本发布 | Anthropic API 推出全新引用功能
目录 VirtualBox 7.1.6 维护版本发布⚙️ 功能改进🛠️ Bug 修复 Anthropic API 推出全新引用功能,让 Claude 的回答更可信 VirtualBox 7.1.6 维护版本发布 VirtualBox 7.1.6 现已发布,这是一个维护版本,主要修复了一些错误并进行…...
JVM_类的加载、链接、初始化、卸载、主动使用、被动使用
①. 说说类加载分几步? ①. 按照Java虚拟机规范,从class文件到加载到内存中的类,到类卸载出内存为止,它的整个生命周期包括如下7个阶段: 第一过程的加载(loading)也称为装载验证、准备、解析3个部分统称为链接(Linking)在Java中数据类型分为基本数据类型和引用数据…...
2025最新版MySQL安装使用指南
2025最新版MySQL安装使用指南 The Installation and Usage Guide of the Latest Version of Oracle MySQL in 2025 By JacksonML 1. 获取MySQL 打开Chrome浏览器,访问官网链接:https://www.mysql.com/ ,随即打开MySQL官网主页面ÿ…...
MIMIC IV数据库中mimiciv_hosp的transfers表的careunit分析
以下是MIMIC IV数据库中mimiciv_hosp的transfers表的careunit的所有值,从医学专业角度分析,下面哪些科室会有实施心脏或神经手术? Cardiac Surgery Cardiac Vascular Intensive Care Unit (CVICU) Cardiology Cardiology Surgery Intermediat…...
AI学习指南HuggingFace篇-Hugging Face 的环境搭建
一、引言 Hugging Face作为自然语言处理(NLP)领域的强大工具,提供了丰富的预训练模型和数据集,极大地简化了开发流程。本文将详细介绍如何搭建适合Hugging Face开发的环境,包括Python环境配置、依赖安装以及推荐的开发工具,帮助读者准备好开发环境。 二、Python环境配置…...
白嫖DeepSeek:一分钟完成本地部署AI
1. 必备软件 LM-Studio 大模型客户端DeepSeek-R1 模型文件 LM-Studio 是一个支持众多流行模型的AI客户端,DeepSeek是最新流行的堪比GPT-o1的开源AI大模型。 2. 下载软件和模型文件 2.1 下载LM-Studio 官方网址:https://lmstudio.ai 打开官网&#x…...
C# dataGridView1获取选中行的名字
在视觉项目中编写的框架需要能够选择产品或复制产品等方便后续换型,视觉调试仅需调试相机图像、调试视觉相关参数、标定,再试跑调试优化参数。 C# dataGridView1 鼠标点击某一行能够计算出是那一行 使用CellMouseClick事件 首先,在Form的构造…...
Day28(补)-【AI思考】-AI会不会考虑自己的需求?
文章目录 AI会不会考虑自己的需求?一、**技术本质:深度≠理解**二、**传播机制:热搜如何制造幻觉**三、**伦理考量:为何必须"撇清"**关键结论 AI会不会考虑自己的需求? 让思想碎片重焕生机的灵魂:…...
幸运数字——蓝桥杯
1.问题描述 哈沙德数是指在某个固定的进位制当中,可以被各位数字之和整除的正整数。例如 126126 是十进制下的一个哈沙德数,因为 (126)10mod(126)0;126 也是八进制下的哈沙德数,因为 (126)10(176)8,(126)10mod(176)…...
快速提升网站收录:避免常见SEO误区
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/26.html 在快速提升网站收录的过程中,避免常见的SEO误区是至关重要的。以下是一些常见的SEO误区及相应的避免策略: 一、关键词堆砌误区 误区描述: 很多…...
[Java]泛型(二)泛型方法
1.定义 在 Java 中,泛型方法是指在方法声明中使用泛型类型参数的一种方法。它使得方法能够处理不同类型的对象,而不需要为每种类型写多个方法,从而提高代码的重用性。 泛型方法与泛型类不同,泛型方法的类型参数仅仅存在于方法的…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
