当前位置: 首页 > news >正文

MFC线程安全案例

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

目录

一、项目解析

二、多线程安全机制

2.1、互斥锁

2.2、临界区

三、项目实现

3.1、在Dialg资源UI控制好界界面

3.2、通过类封装 线程操作

3.3、通过消息映射机制处理点击事件

3.4、项目演示 


一、项目解析

这里我们就要控制线程对临界资源的访问,通过UI界面进行测试。 

二、多线程安全机制

在多线程中,当多个线程访问同一份临界资源的时候,可能会出现线程之间竞争的操作,比如有二个线程A和B,他们都要进行抢票操作, int ticket=5000,线程每次抢到一份票就进行ticket--操作。

每个线程中都有下面的判断

while (1)
{if (ticket <= 0){std::cout << "停止抢票"<<std::endl;break;}else{--ticket;}
}

当没有票的时候,就停止抢票。但是我们发现ticket会出现负数的情况。按理说是这是不可能的,为什么呢?这是因为--操作是非原子的。

原子操作(Atomic Operation)

原子操作是指一组操作在执行过程中不可被中断的操作。无论在多线程或并发环境中,原子操作都是一个不可分割的单位,即使在操作执行过程中,其他线程也无法干扰该操作。这保证了操作的完整性和一致性。

非原子操作(Non-Atomic Operation)

非原子操作是指在执行过程中可能会被其他线程或进程中断的操作。在多线程或并发环境下,非原子操作可能会导致数据竞争、冲突或不一致的结果。

那为什么会出现负数呢?

 首先,我们来分析一下非原子操作:

--ticket;  // 非原子操作

这个操作实际上包含了三个步骤: 

  • 读取 ticket 的当前值。
  • 对该值进行减 1 操作。
  • 将新的值写回 ticket

在多线程环境中,当多个线程(比如线程A和线程B)同时执行这段代码时,可能会出现以下情况:

场景示例:

假设 ticket 的值为 1,线程 A 和线程 B 同时开始抢票。

  • 线程A 读取了 ticket 的值为1,刚刚执行完--操作,但是还没有来的急,将信息写会回内存变量,OS(操作系统就调度线程B进行抢票)
  • 线程B此时发现ticket 的值为1,可以进行抢票,线程B也执行了--操作。
  • 这个时候线程A的时间片到了,继续执行后面的操作把结果写会变量为0
  • 这个线程B继续往后执行,也讲--的结果写会,最终ticketw为服数

这样,ticket 同一份资源被用了二个线程同时使用,造成了资源不一致,当多个线程都在进行类似的操作时,ticket 的值有可能被多次修改,最终可能导致 ticket 为负数。

为了解决在多线程中,那些线程不安全的问题,我们要使用下面的安全操作

2.1、互斥锁

互斥锁(Mutex,全称 Mutual Exclusion Lock)是一种用于 多线程同步 的机制,主要用于保护共享资源,确保同一时刻只有一个线程能够访问共享数据,从而避免数据竞争和不一致性。互斥锁通过加锁和解锁操作来控制对临界区的访问。

C++11 引入了 std::mutex 类来提供互斥锁的功能。它位于 <mutex> 头文件中。

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;  // 创建一个互斥锁void printHello() {mtx.lock();  // 加锁std::cout << "Hello from thread!" << std::endl;mtx.unlock();  // 解锁
}int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

 使用 std::lock_guard 自动管理锁

std::lock_guard 是一个 RAII(Resource Acquisition Is Initialization)类,它用于自动加锁和解锁。std::lock_guard 在创建时会加锁,在销毁时会自动解锁。这种方式更安全,避免了手动解锁时可能出现的错误(例如忘记解锁

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void printHello() {std::lock_guard<std::mutex> lock(mtx);  // 自动加锁std::cout << "Hello from thread!" << std::endl;
}  // 自动解锁int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

std::unique_lock:更灵活的锁管理 

std::unique_lock 是另一种锁管理类,它提供了更多的功能,如可以手动解锁、延迟加锁、以及可以在多个锁上进行组合。

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;void printHello() {std::unique_lock<std::mutex> lock(mtx);  // 加锁std::cout << "Hello from thread!" << std::endl;// lock 会在作用域结束时自动解锁
}int main() {std::thread t1(printHello);std::thread t2(printHello);t1.join();t2.join();return 0;
}

锁的死锁(Deadlock) 

死锁是指两个或多个线程因相互等待而无法继续执行的情况。例如,线程 A 持有锁 1,等待锁 2;线程 B 持有锁 2,等待锁 1。这样,两个线程相互等待对方释放锁,导致程序进入死锁状态。

2.2、临界区

临界区是指一段在多线程程序中访问共享资源的代码区域,多个线程必须互斥地访问这个区域,以避免出现数据竞争和资源冲突。简单来说,临界区是多个线程共享的数据被操作的区域,这些数据需要保护起来,以确保在任何时刻只有一个线程能够访问和修改这些共享数据。

Windows 操作系统中,创建和使用临界区的 API 提供了多线程同步的机制。Windows 提供了 CRITICAL_SECTION 类型,它是操作系统用于线程同步的基本工具。CRITICAL_SECTION 用于保护共享资源,确保同一时刻只有一个线程能够进入临界区。

1. 初始化临界区

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

lpCriticalSection:指向一个 CRITICAL_SECTION 结构的指针,系统会根据这个结构初始化临界区。 

2. 删除临界区

当不再需要临界区时,应该调用 DeleteCriticalSection 来销毁它。这样可以释放与临界区相关的资源。

void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

3. 加锁临界区 

使用 EnterCriticalSection 函数来加锁临界区。当一个线程试图访问临界区时,它必须首先请求锁。如果锁已经被其他线程持有,则该线程将会被阻塞,直到锁变得可用。 

void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

 4. 解锁临界区

使用 LeaveCriticalSection 来解锁临界区,这样其他线程可以访问临界区中的资源。

void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

要使用临界资源,我们要先定义应该临界区,比如: CRITICAL_SECTION m_critical_section对象

,然后调用相应的函数就好了。如EnterCriticalSection(&m_CriticalSection);

 2.3、条件变量

在了解条件变量时,我们先了解这样的场景。在多线程中,一个线程负责消费,一个线程负责生产资源。但是这里会存在一个问题。消费的线程怎么知道,还有没有资源给他消息。生产资源的线程,怎么知道资源是不是生产足够了。为了解决这个问题,引入了条件变量(本质就是多线程间的通信)。

条件变量:它让线程可以在某些特定的条件下被“挂起”(阻塞),并在条件发生变化时被其他线程“唤醒”或“通知”继续执行。

条件变量,通常与互斥锁(std::mutex)配合使用,用于保护共享资源并避免数据竞争。

C++ 标准库提供了 std::condition_variable 类来实现这一机制。下面是一个简单的示例,展示了如何使用条件变量来实现线程之间的同步。

.h文件 

#pragma once#include<iostream>
#include<queue>
#include<thread>
#include<condition_variable>
#include<mutex>namespace pjb
{class ProductConsumer{public:void producter();//生产者void consumer();//消费者private:std::queue<int> m_qdataQueue;std::mutex m_mutex;std::condition_variable m_cv;};
}

.cpp文件 

#include"thread_scure.h"void pjb::ProductConsumer::producter()
{//生产者生产数据,生产好了,才会通知消费者去消费for (int i = 0; i < 5; ++i){//用休眠,模拟生产过程std::this_thread::sleep_for(std::chrono::seconds(1));{//这个区间要进行加锁保护std::lock_guard<std::mutex> lock(m_mutex);m_qdataQueue.push(i);//生产数据std::cout << "data: " << i << std::endl;}//数据生产好了,就可以通知消费者,进行消费了m_cv.notify_all();//通知消费者}
}void pjb::ProductConsumer::consumer()
{while (true){//加锁//注意我们这里加锁使用的是unique_lock,因为wait需要//wait() 会在等待时释放互斥锁,并且能够在被唤醒后重新获得锁。std::unique_lock<std::mutex> lock(m_mutex);//设置一个条件变量,有数据就消费,没有数据就阻塞等待//当wait的第二个参数返回true才会继续执行m_cv.wait(lock, [this] {return !m_qdataQueue.empty();});//消费数据int data = m_qdataQueue.front();m_qdataQueue.pop();std::cout << "Consumed: " << data << std::endl;if (m_qdataQueue.empty()){break;  // 如果队列为空,则退出}}
}

 mian.cpp文件

#include"thread_scure.h"int main()
{pjb::ProductConsumer pc;// 启动生产者和消费者线程std::thread prod(&pjb::ProductConsumer::producter, &pc);std::thread cons(&pjb::ProductConsumer::consumer, &pc);// 等待线程完成prod.join();cons.join();return 0;
}

1、在这里例子中,我定义了m_mutex锁和m_cv的条件变量

2、在生产者线程中,我们让他进行生产,将成产的数据写入到  队列中  m_qdataQueue

3、在消费者线程中,我们让他通过条件变量    m_cv.wait(lock, [this] {return !m_qdataQueue.empty();});去等待资源,如果队里不为空就往下执行,否则阻塞等待资源。

4、通过上面的例子,我们就能使用条件变量可以在线程间协调协作,使得线程可以根据某些条件的改变而被唤醒或等待。

运用场景 

1. 生产者-消费者问题

这是使用条件变量的经典场景,生产者线程负责生产数据并放入队列,消费者线程负责从队列中取数据进行消费。消费者线程在队列为空时需要等待,直到生产者线程将数据放入队列中并通知消费者线程继续工作。

示例:

  • 生产者线程生产数据,放入队列。
  • 消费者线程等待队列非空条件,直到生产者通知它有数据可以消费。

2. 线程池中的任务调度

在多线程编程中,线程池通常用来管理并发任务。条件变量可以用于控制线程池中的线程何时开始执行任务以及何时停止。线程池中的工作线程会等待条件变量通知,直到任务队列中有新的任务时,它们才会开始执行。

示例:

  • 线程池中的线程在没有任务时等待。
  • 主线程将任务加入任务队列,并通过条件变量通知等待的线程开始工作。

3. 读写锁模式

在某些场景中,多个线程需要读取共享数据,少数线程需要写入数据。在这种情况下,条件变量可以用于实现线程之间的协调,允许多个线程同时读取共享数据,而在有线程写入时,阻塞其他线程的读取操作,直到写入完成。

示例:

  • 读者线程等待写者线程完成写入操作。
  • 写者线程完成写入后,通过条件变量通知等待的读者线程继续读取。

4. 限制并发数

有时需要限制并发执行的线程数,例如限制最大并发的数据库查询线程数。在这种情况下,条件变量可以用于协调多个线程的启动和停止,确保不会超过最大并发数。

示例:

  • 启动多个线程执行任务,但限制同时执行的最大线程数。
  • 如果超过最大线程数,新的线程等待直到有线程完成并释放资源。

5. 定时任务或超时等待

条件变量还可以用于处理定时任务或设置超时等待。例如,一个线程等待某个条件的满足,但如果条件在一定时间内未满足,则超时退出。条件变量与 std::chrono 配合使用可以实现这一功能。

示例:

  • 线程在等待某个条件时设置超时时间,如果超时则执行其他操作。
  • 线程等待指定时间,直到收到条件变量的通知。

6. 事件通知

在某些场景中,一个线程可能会等待另一个线程的某个事件发生,典型的例子如状态机或事件驱动的程序。在这种情况下,条件变量用于实现线程间的通知机制。

示例:

  • 主线程等待子线程完成某个操作(如处理某个事件或任务)。
  • 一旦子线程完成,主线程通过条件变量通知其继续执行。

2.4、信号量

信号量是一种用于进程间或线程间同步的机制。它是一种计数器,通常用于控制访问共享资源的线程数量

号量的主要作用就是 控制线程对临界资源的访问,并通过限制线程数量来 同步并发访问。它允许多个线程按一定规则同时访问有限数量的资源,或者确保只有一定数量的线程可以同时访问共享资源,从而避免数据竞争或资源冲突。

在C++11中是没有信号量这个概念的,但是我们可以通过互斥锁mutex和条件变量conitation_variable来实现。

下面是信号量的简单模拟实现

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>class Semaphore {
public:explicit Semaphore(int count = 0) : count(count) {}void wait() {std::unique_lock<std::mutex> lock(mtx);// 等待直到 count 大于 0cond.wait(lock, [this]() { return count > 0; });--count;}void signal() {std::lock_guard<std::mutex> lock(mtx);++count;cond.notify_one();  // 唤醒一个等待的线程}private:int count;std::mutex mtx;std::condition_variable cond;
};void worker(int id, Semaphore& sem) {sem.wait();  // 等待信号量std::cout << "Thread " << id << " is working\n";std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟工作std::cout << "Thread " << id << " is done\n";sem.signal();  // 完成后释放信号量
}int main() {Semaphore sem(2);  // 最多允许 2 个线程同时执行std::thread t1(worker, 1, std::ref(sem));std::thread t2(worker, 2, std::ref(sem));std::thread t3(worker, 3, std::ref(sem));std::thread t4(worker, 4, std::ref(sem));t1.join();t2.join();t3.join();t4.join();return 0;
}

信号量通常有两个操作:

  • P 操作(Wait/Decrement):将信号量的值减 1,如果信号量的值为 0,则调用线程会被阻塞,直到信号量大于 0。
  • V 操作(Signal/Increment):将信号量的值加 1,可能会唤醒一个等待的线程。

三、项目实现

为了实现这个项目,我们通过MFC创建应该基于对话框的项目。

3.1、在Dialg资源UI控制好界界面

这里要注意为对话框添加变量名,因为我要显示线程运行的数据在上面。

这里我们在对话框的.h头文件中可以看到 

3.2、通过类封装 线程操作

 ThreadMagager.h在这个头文件中实现

#pragma once#include<functional>
#include<thread>
#include<atomic>
#include <windows.h> 
#include <memory> 
namespace pjb
{class ThreadManager{public:ThreadManager();~ThreadManager();void StartThread(int step, unsigned int interval, std::function<void(int)> callback);void StopThread();bool IsRunning()const { return m_Running; };private:std::thread m_Thread;//std::unique_ptr<std::thread> m_Thread;std::atomic<bool> m_Running;//创建原子对象,保证线程安全int m_iCount;//计数器int m_iStep;//步长unsigned int m_uiInterval;//时间间隔std::function<void(int)> m_UpdateCallback;//更新回调函数CRITICAL_SECTION m_CriticalSection; // 临界区,用于线程同步void _ThreadProc();};
}

在 ThreadMagager.cpp中实现细节操作


#include "pch.h"//这个头我文件记得包含
#include"ThreadMagager.h"
#include<chrono>// 构造函数:初始化成员变量和临界区
pjb::ThreadManager::ThreadManager(): m_Running(false), m_iCount(0), m_iStep(0), m_uiInterval(0)
{InitializeCriticalSection(&m_CriticalSection); // 初始化临界区
}// 析构函数:停止线程并销毁临界区
pjb::ThreadManager::~ThreadManager()
{StopThread();DeleteCriticalSection(&m_CriticalSection); // 销毁临界区
}
void pjb::ThreadManager::StartThread(int step, unsigned int interval, std::function<void(int)> callback)
{//判断线程是否已经启动if (m_Running) return;//设置线程的参数m_iStep = step;m_uiInterval = interval;m_UpdateCallback = callback;m_Running = true;//启动线程// 使用智能指针来管理资源,防止内存泄漏//m_Thread = std::make_unique<std::thread>(&ThreadManager::ThreadProc, this);//std::thread t(function, args...);的参数//function为可调用对象,将会做为线程的主体函数。传this指针,是为了告诉线程在哪个 ThreadManager 对象上调用这个成员函数m_Thread = std::thread(&pjb::ThreadManager::_ThreadProc, this);}
void pjb::ThreadManager::StopThread()
{if (!m_Running)return;m_Running = false;//判断线程是否能被join()。if (m_Thread.joinable()){//m_Thread.join();//等待线程完成,这种方式会阻塞UI线程,导致用户无法操作m_Thread.detach();  // 分离线程,让它在后台执行,UI线程可以继续操作,当程序结束后,操作系统会回收资源}
}
//线程处理
void pjb::ThreadManager::_ThreadProc()
{while (m_Running){// 使用临界区保护共享资源EnterCriticalSection(&m_CriticalSection);// 检查计数器是否已经达到上限if (m_iCount >= 10000){m_Running = false; // 停止线程LeaveCriticalSection(&m_CriticalSection);  // 离开临界区break; // 退出循环}//更新计数器m_iCount += m_iStep;LeaveCriticalSection(&m_CriticalSection); // 离开临界区//调用回调函数,更新计数值if (m_UpdateCallback){m_UpdateCallback(m_iCount);}//休眠指定时间,然后继续进行更新std::this_thread::sleep_for(std::chrono::milliseconds(m_uiInterval));//毫秒}
}

这里我们在 线程处理函数_ThreadProc()中创建临界区,限制了访问临界资源时候只运行一个线程访问。

3.3、通过消息映射机制处理点击事件

对于对话框的头文件没有什么好说的,大家看一下就可以了。

// ThreadOperateDlg.h: 头文件
//#pragma once
#include"ThreadMagager.h"// CThreadOperateDlg 对话框
class CThreadOperateDlg : public CDialogEx
{
// 构造
public:CThreadOperateDlg(CWnd* pParent = nullptr);	// 标准构造函数// 对话框数据
#ifdef AFX_DESIGN_TIMEenum { IDD = IDD_THREADOPERATE_DIALOG };
#endifprotected:virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持// 实现
protected:HICON m_hIcon;// 生成的消息映射函数virtual BOOL OnInitDialog();afx_msg void OnSysCommand(UINT nID, LPARAM lParam);afx_msg void OnPaint();afx_msg HCURSOR OnQueryDragIcon();DECLARE_MESSAGE_MAP()
private:pjb::ThreadManager m_Thread1;pjb::ThreadManager m_Thread2;CEdit m_show1;CEdit m_show2;
public:afx_msg void OnBnClickedButton1();afx_msg void OnBnClickedButton2();

在对话框.cpp文件中处理点击事情,就是通过前面我们封装好的接口去处理即可

//线程启动
void CThreadOperateDlg::OnBnClickedButton1()
{// TODO: 在此添加控件通知处理程序代码//启动线程//情况2使用共享计数变量:m_Thread1.StartThread(1, 10, [this](int value) {CString str;//格式转换,将整形转换为字符串str.Format(_T("%d"), value);GetDlgItem(IDC_EDIT1)->SetWindowTextW(str);if (_ttoi(str) >= 10000){//将启动线程的按键禁用GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);}});//线程2m_Thread2.StartThread(2, 20, [this](int value) {CString str;//格式转换,将整形转换为字符串str.Format(_T("%d"), value);GetDlgItem(IDC_EDIT2)->SetWindowTextW(str);if (_ttoi(str) >= 10000){//将启动线程的按键禁用GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);}});}void CThreadOperateDlg::OnBnClickedButton2()
{// TODO: 在此添加控件通知处理程序代码m_Thread1.StopThread();m_Thread2.StopThread();
}

3.4、项目演示 

这里我们点击启动线程,就可以观察到对话框的变化。

相关文章:

MFC线程安全案例

作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、项目解析 二…...

spring cloud和spring boot的区别

Spring Cloud和Spring Boot在Java开发领域中都是非常重要的框架&#xff0c;但它们在目标、用途和实现方式上存在明显的区别。以下是对两者区别的详细解析&#xff1a; 1. 含义与定位 Spring Boot&#xff1a; 是一个快速开发框架&#xff0c;它简化了Spring应用的初始搭建以…...

探索后端开发中的异步API:基于Resilience4j与Reactive Programming的高性能设计

引言 随着微服务架构的普及&#xff0c;后端系统面临的挑战愈发严峻&#xff0c;尤其是在高并发和高可用性方面。传统的同步调用模式虽然简单&#xff0c;但在处理大量并发请求时可能会成为瓶颈。为了应对这一问题&#xff0c;异步编程逐渐成为后端开发的热门话题。 在本文中…...

JDK 17 和 JDK 21 在垃圾回收器(GC)上有什么优化?如何调整 GC 算法以提升应用性能?

JDK 17 和 JDK 21 在垃圾回收器&#xff08;GC&#xff09;上有什么优化&#xff1f;如何调整 GC 算法以提升应用性能&#xff1f; 本文将从 JDK 17 与 JDK 21 的垃圾回收改进出发&#xff0c;结合代码示例解析优化方案&#xff0c;并提供实际项目中的调优策略&#xff0c;帮助…...

两个角度理解「交叉熵损失函数」

目录 前言一、交叉熵 角度1、计算机基础&#xff08;1&#xff09;编码&#xff08;2&#xff09;数据分布 2、熵 相关2.1 信息量2.2 信息熵2.3 相对熵2.4 最小化「相对熵」还是「交叉熵」 3、公式推导3.1 信息量3.2 信息熵3.3 相对熵 二、极大似然估计 角度1、似然函数1.1 二次…...

深挖vue3基本原理之一 —— 响应式系统(Reactivity System)

响应式系统&#xff08;Reactivity System&#xff09; 1.1 基于 Proxy 的响应式代理 在 Vue 3 中&#xff0c;响应式系统的核心是使用 ES6 的 Proxy 来替代 Vue 2 里的 Object.defineProperty 方法&#xff0c;以此实现更加全面和强大的响应式追踪功能。下面我们来详细剖析这…...

解锁Rust:融合多语言特性的编程利器

如果你曾为理解Rust的特性或它们之间的协同工作原理而苦恼,那么这篇文章正是为你准备的。 Rust拥有许多令人惊叹的特性,但这些特性并非Rust所独有。实际上,Rust巧妙地借鉴了众多其他语言的优秀特性,并将它们融合成了一个完美的整体。深入了解Rust这些重要特性的来源以及它是…...

AI编程01-生成前/后端接口对表-豆包(或Deepseek+WPS的AI

前言: 做过全栈的工程师知道,如果一个APP的项目分别是前端/后端两个团队开发的话,那么原型设计之后,通过接口文档进行开发对接是非常必要的。 传统的方法是,大家一起定义一个接口文档,然后,前端和后端的工程师进行为何,现在AI的时代,是不是通过AI能协助呢,显然可以…...

[AUTOSAR通信] - PDUR模块解读

点击订阅专栏不迷路 文章目录 一、 PDUR模块概述二、功能描述2.1 发送路由功能2.2 接收路由功能2.3 网关路由功能2.4 路由控制功能 三、配置項介紹3.1. PduRBswModules3.2. PduRGeneral3.3. PduRRoutingTables3.4. PduRRoutingPath3.5. PduRSrcPdu3.6. PduRDestPdu 四、总结 &g…...

伺服报警的含义

前言&#xff1a; 大家好&#xff0c;我是上位机马工&#xff0c;硕士毕业4年年入40万&#xff0c;目前在一家自动化公司担任软件经理&#xff0c;从事C#上位机软件开发8年以上&#xff01;我们在开发C#的运动控制程序的时候&#xff0c;一个必要的步骤就是设置伺服报警信号的…...

物联网(IoT)如何与人工智能(AI)的结合

物联网&#xff08;IoT&#xff09;与人工智能&#xff08;AI&#xff09;的结合是当前技术发展的重要趋势&#xff0c;通常被称为 AIoT&#xff08;人工智能物联网&#xff09;。这种结合通过将AI的计算能力和数据分析能力与物联网的海量设备连接能力相结合&#xff0c;实现了…...

有哪些PHP开源框架属于是“高开疯走”的?

“高开疯走”是一个网络流行语或者谐音梗。可能是指一开始起点很高&#xff08;高开&#xff09;&#xff0c;然后发展迅速或者变得非常牛&#xff08;疯走&#xff09;。 在PHP生态中&#xff0c;一些框架面对市场的风起云涌&#xff0c;能持续保持高质量发展&#xff0c;切实…...

本地部署DeepSeek摆脱服务器繁忙

由于图片和格式解析问题&#xff0c;可前往 阅读原文 最近DeepSeek简直太火了&#xff0c;频频霸榜热搜打破春节的平静&#xff0c;大模型直接开源让全球科技圈都为之震撼&#xff01;再次证明了中国AI的换道超车与崛起 DeepSeek已经成了全民ai&#xff0c;使用量也迅速上去了…...

Miniforge —— 轻量化的 conda 解决方案

引言 在日常使用中&#xff0c;我们常常使用 Anaconda 或 Miniconda 来管理 Python 环境和包。但由于 Anaconda/Miniconda 属于商业产品&#xff0c;当企业规模超过一定人数时就会涉及付费问题。相比之下&#xff0c;Miniforge 是由社区主导维护的一个完全免费的替代方案&…...

GO语言基础知识

一、引言 在当今快速发展的软件开发领域&#xff0c;Go语言&#xff08;又称Golang&#xff09;凭借其简洁的语法、强大的并发支持和高效的性能&#xff0c;逐渐成为许多开发者的首选编程语言之一。Go语言由Google团队开发&#xff0c;自2009年发布以来&#xff0c;已经在云原…...

Electron 全面解析:跨平台桌面应用开发指南

引言 在当今多平台并存的数字时代&#xff0c;如何高效开发跨平台桌面应用成为开发者面临的重要挑战。Electron作为GitHub开源的跨平台框架&#xff0c;凭借其独特的Web技术融合能力&#xff0c;已成为构建桌面应用的热门选择。本文将深入探讨Electron的核心原理、开发实践及未…...

git 克隆指定 tag 的项目

git 克隆指定 tag 的项目 一、克隆指定tag的项目二、验证克隆结果 一、克隆指定tag的项目 以 tinyxml2项目 为例说明&#xff1a; git clone --branch V10.0.0 https://github.com/leethomason/tinyxml2.git解释&#xff1a; git clone&#xff1a;这是克隆一个远程仓库的命…...

pytorch笔记:mm VS bmm

1 bmm (batch matrix multiplication) 批量矩阵乘法&#xff0c;用于同时处理多个矩阵的乘法bmm 的输入是两个 3D 张量&#xff08;batch of matrices&#xff09;&#xff0c;形状分别为 (batch_size, n, m) 和 (batch_size, m, p)bmm 输出的形状是 (batch_size, n, p) 2 mm…...

《qt open3d中添加最远点采样》

qt open3d中添加最远点采样 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionFilterFarthestDownSample_triggered();void MainWindow::on_...

自然语言处理NLP入门 -- 第二节预处理文本数据

在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;数据的质量直接影响模型的表现。文本预处理的目标是清理和标准化文本数据&#xff0c;使其适合机器学习或深度学习模型处理。本章介绍几种常见的文本预处理方法&#xff0c;并通过 Python 代码进行示例。 2.1 文本清理…...

depcheck检查node.js项目中未使用和缺失依赖的工具

depcheck检查node.js项目中未使用和缺失依赖的工具 一、安装二、使用方法 depcheck 是一个用于检查 Node.js 项目中未使用依赖项和缺失依赖项的工具。以下为你详细介绍它的相关信息、使用方法和作用。 主要作用: 1.发现未使用的依赖 在项目开发过程中&#xff0c;我们可能会安…...

正则表达式(竞赛篇)

为了更深入了解正则表达式&#xff0c;我们需要首先学习与正则表达式有关的类以及方法。如Pattern和Matcher类&#xff0c;以及部分字符串方法。 我们这里先将简单的字符串方法(String类)进行讲解 在Java中&#xff0c;String类提供了许多用于字符串操作的方法&#xff0c;其中…...

国科大 2024-2025秋 大数据分析课程期末复习重点

教师&#xff1a;靳小龙、刘盛华 博主在做期末复习时&#xff0c;发现这门课的资料少之又少&#xff0c;搜遍全网只能找到几份作业答案。特此将本学期老师画的重点分享给学弟学妹们&#xff0c;希望对大家的复习有所帮助。 靳小龙老师部分&#xff1a; 大数据与大数据分析简…...

使用Python爬虫获取淘宝Custom API接口数据

一、引言 淘宝作为中国最大的电商平台之一&#xff0c;其提供的API接口为开发者提供了丰富的数据访问能力。通过淘宝的Custom API接口&#xff0c;开发者可以获取商品详情、店铺信息、订单数据等多种资源。这些数据对于电商运营、市场分析、竞品监控等场景具有极高的价值。本文…...

人生的转折点反而迷失了方向

就像我老婆说的&#xff0c;我是抽空结了一个婚。今天是上班的第三天&#xff0c;不知道是出于何种原因&#xff0c;自己反而陷入了深深的困境&#xff0c;没有了斗志&#xff0c;原因也找不出来&#xff0c;白天在公司没有很大量的产出&#xff0c;晚上回去是想学一学&#xf…...

Web应用项目开发 ——Spring Boot邮件发送

一.邮件发送介绍 邮件发送是一个非常常见的功能&#xff0c;注册时的身份认证、重要通知发送等都会用到邮件发送。在现代的Web应用程序中&#xff0c;邮件发送功能是非常常见且重要的一部分&#xff0c;Spring Boot框架提供了简单且强大的方式来实现邮件发送功能。Spring中提供…...

mit6.824-lab1

1.任务及要求 https://pdos.csail.mit.edu/6.824/labs/lab-mr.html 2 Coordinator 与 Worker 的设计分析 2.1 Coordinator&#xff08;协调器&#xff09;的核心职责 协调器是 MapReduce 系统的核心控制节点&#xff0c;负责全局任务调度与状态管理&#xff0c;具体功能如下…...

Vision Transformer:打破CNN垄断,全局注意力机制重塑计算机视觉范式

目录 引言 一、ViT模型的起源和历史 二、什么是ViT&#xff1f; 图像处理流程 图像切分 展平与线性映射 位置编码 Transformer编码器 分类头&#xff08;Classification Head&#xff09; 自注意力机制 注意力图 三、Coovally AI模型训练与应用平台 四、ViT与图像…...

linux查看所有程序占用的本地端口

sudo ss -tulwnp ss是Socket Statistics的缩写&#xff0c;用来替代旧的netstat工具&#xff0c;功能更强大&#xff0c;执行更快。它用于查看系统的网络连接情况&#xff0c;包括TCP、UDP等协议的信息。 查阅ss的帮助文档&#xff08;man ss&#xff09;&#xff0c;发现选项…...

java后端开发day15--字符串(一)

&#xff08;以下内容全部来自上述课程&#xff09; 1.API &#xff08;Application Programming Interface 应用程序编程接口&#xff09; 1.简单理解 简单理解&#xff1a;API就是别人已经写好的东西&#xff0c;我们不需要自己编写&#xff0c;直接使用即可。 Java API&…...

C++STL容器之map的使用及复现

map 1. 关联式容器 vector、list、deque、forward_list(C11) 等STL容器&#xff0c;其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身&#xff0c;这样的容器被统称为序列式容器。而 map、set 是一种关联式容器&#xff0c;关联式容器也是用来存储数据的&#xf…...

lvs的DR模式

基于Linux的负载均衡集群软件 LVS 全称为Linux Virtual Server,是一款开源的四层(传输层)负载均衡软件 Nginx 支持四层和七层(应用层)负载均衡 HAProxy 和Nginx一样,也可同时支持四层和七层(应用层)负载均衡 基于Linux的高可用集群软件 Keepalived Keepalived是Linux…...

FlutterWeb实战:07-自动化部署

Flutter Web 开发打包后&#xff0c;可以手动发布到服务器上&#xff0c;通过 nginx 来托管静态页面。本文将介绍如何将这一过程自动化。 整体思路 使用脚本自动化构建&#xff0c;然后使用 Docker 打包成镜像&#xff0c;最后部署在服务器上。 自动化构建 这里使用 GitLab-…...

剑指 Offer II 020. 回文子字符串的个数

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20020.%20%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E4%B8%AA%E6%95%B0/README.md 剑指 Offer II 020. 回文子字符串的个数 题目描述 …...

vue中附件下载及打印功能

1.附件dom 注&#xff1a;fileList是由后台返回的附件数组&#xff0c;数组中包含附件名称fileName,附件地址url&#xff0c;附件id等信息 <el-form-item label"附件" style"width: 100% !important;" v-if"modelTypeborrowDetail"><d…...

Python(十九)实现各大跨境船公司物流查询数据处理优化

一、前言 之前已经实现了常用 跨境物流船司 基础信息查询功能&#xff0c;如下所示 实现各大跨境船公司[COSCO/ZIM/MSK/MSC/ONE/PIL]的物流信息查询&#xff1a;https://blog.csdn.net/Makasa/article/details/145484999?spm1001.2014.3001.5501 然后本章在其基础上做了一些…...

android 安装第三方apk自动赋予运行时权限

摘要&#xff1a;行业机使用场景点击运行时权限很麻烦&#xff0c;而随着android的演进&#xff0c;对于权限的管控越发严格。故本文通过对系统的修改实现第三方app在运行时直接获取全部权限。 通过属性ro.perms.force_grant控制功能开关。 Index: frameworks/base/services/…...

java8、9新特性

JAVA8 Lambda 表达式 (parameters) -> expression 或 (parameters) ->{ statements; } 提供了一种更为简洁的语法&#xff0c;尤其适用于函数式接口。相比于传统的匿名内部类&#xff0c;Lambda 表达式使得代码更为紧凑&#xff0c;减少了样板代码的编写。 它允许将函…...

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<9>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节是对之前内容的修整 目录 一、传值调用和传址调用二、数组名的理解三、指针访问数组四、结尾 一…...

Java网络编程入门

网络编程是指通过计算机网络进行数据传输和通信的过程。在Java中&#xff0c;网络编程提供了一套强大的API&#xff0c;使得开发者能够轻松地创建网络应用程序。本文将介绍Java网络编程的基本概念和一些常用的类。 1.网络编程的基本概念 在网络编程中&#xff0c;我们通常需要…...

2.4 构建模块化应用

第4章&#xff1a;构建模块化应用 模块化应用是 JDK 9 的核心特性之一&#xff0c;通过模块化系统&#xff08;Project Jigsaw&#xff09;实现代码的强封装和显式依赖管理。本章详细讲解如何从零构建一个模块化应用&#xff0c;包括模块定义、编译、打包、运行及调试。 4.1 模…...

14.1 Auto-GPT 项目定位与价值解读:揭开自主智能体的神秘面纱

Auto-GPT 项目定位与价值解读:揭开自主智能体的神秘面纱 关键词:Auto-GPT 核心机制、自主任务分解、LangChain 智能体、持续自我优化、AGI 实践路径 一、为什么 Auto-GPT 能引爆技术圈? 1.1 从工具到员工的范式转移 维度传统AI系统Auto-GPT 智能体输入方式精确指令(“翻译…...

【Elasticsearch】分析器的构成

在Elasticsearch中&#xff0c;分析器&#xff08;Analyzer&#xff09;是一个处理文本数据的管道&#xff0c;它将输入的文本转换为一系列词元&#xff08;tokens&#xff09;&#xff0c;并可以对这些词元进行进一步的处理和规范化。分析器由以下三个主要组件构成&#xff1a…...

2025 年前端开发现状分析:卷疯了还是卷麻了?

一、前端现状&#xff1a;框架狂飙&#xff0c;开发者崩溃 如果你是个前端开发者&#xff0c;那么你大概率经历过这些场景&#xff1a; 早上打开 CSDN&#xff08;或者掘金&#xff0c;随便&#xff09;&#xff0c;发现又有新框架发布了&#xff0c;名字可能是 VueXNext.js 之…...

单例模式详解(Java)

单例模式详解(Java) 一、引言 1.1 概述单例模式的基本概念和重要性 单例模式是一种常用的软件设计模式,它确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点来访问这个唯一实例。这种模式在资源管理、配置设置和日志记录等方面非常有用,因为它们通常只需要…...

后端面试题

以下是一些常见的后端面试题: 一、通用基础 请简述HTTP协议的工作原理。 答案: HTTP是基于请求 - 响应模型的协议。客户端(通常是浏览器)向服务器发送一个HTTP请求,请求包含请求行(包含请求方法,如GET、POST等、请求的URL和HTTP版本)、请求头(包含诸如浏览器类型、接…...

深入理解Linux网络随笔(一):内核是如何接收网络包的(上篇)

深入理解Linux网络随笔&#xff08;一&#xff09;&#xff1a;内核是如何接收网络包的&#xff08;上篇&#xff09; 1、TCP/IP模型概述 从Linux视角看&#xff0c;TCP/IP网络分层模型包括用户空间和内核空间。用户空间&#xff08;应用层&#xff09;负责HTTP、FTP等协议的…...

SQL-leetcode—1393. 股票的资本损益

1393. 股票的资本损益 Stocks 表&#xff1a; ---------------------- | Column Name | Type | ---------------------- | stock_name | varchar | | operation | enum | | operation_day | int | | price | int | ---------------------- (stock_name, operation_day) 是这张…...

热更图片方案

项目平常需要对线上一些图片资源修正&#xff0c;所以需要热更图片功能。 远端入口新增字段配json文件 {"1.1.22030303":{"sprite":{"assets/ui/common/images/acient_gold.png" : "https://aaaa.png","assets/ui/common/image…...

Flutter PIP 插件 ---- iOS Video Call

以下是一篇关于在 iOS 中实现画中画(PiP)功能的技术博客: iOS 画中画(PiP)功能实现指南 简介 画中画(Picture in Picture, PiP)是一项允许用户在使用其他应用时继续观看视频内容的功能。本文将详细介绍如何在 iOS 应用中实现 PiP 功能。 系统要求 iOS 15.0 及以上版本AVKi…...