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

Windows系统编程(八)线程同步

线程安全问题

每个线程都有自己独立的堆栈,局部变量是存储在栈中的,这就意味着每个线程都会有一份自己的局部变量,当线程仅仅访问自己的局部变量时就不存在线程安全问题。但是全局变量是存储在全局区的,多线程共享全局变量,当多个线程共用一个全局变量进行非读行为时就会存在线程安全问题

如下所示代码,我们创建了两个线程并写了一个线程函数,该函数的作用就是使用全局变量,模拟售卖物品。全局变量countNumber表示该物品的总量,其值是10。如果有多个地方(线程)去卖(使用)这个物品(全局变量)时,就会出现差错:

#include <windows.h>int countNumber = 10;DWORD WINAPI ThreadProc(LPVOID lpParameter) {while (countNumber > 0) {printf("Sell num: %d\n", countNumber);// 售出-1countNumber--;printf("Count: %d\n", countNumber);}return 0;
}int main(int argc, char* argv[])
{HANDLE hThread;hThread = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);HANDLE hThread1;hThread1 = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);CloseHandle(hThread);getchar();return 0;
}

如图,我们运行了代码,发现会出现重复售卖,并且到最后总数竟变成了-1。

这是因为多线程在执行代码的时候是可以随时切换线程的,而不是等一个线程执行完毕以后再切换另一个线程执行。只有一个线程执行完毕以后再去执行另一个线程,这样才是线程安全。

为实现线程安全,有以下几种方法

临界区

一次只允许一个线程使用的资源叫做临界资源,而访问临界资源的程序,称之为临界区。通过临界区便可以很好的解决线程安全问题。

假设有一个全局变量令牌,线程只有获取了这个令牌,才能访问全局变量X。当线程1获取了这个令牌时,令牌的值修改为0,表该令牌已经被线程1所有,然后线程1会执行代码去访问全局变量X,最后归还令牌,令牌的值修改为1。在这个过程中,其他线程会根据令牌的值判断是否可以获取令牌进而判断是否可以访问全局变量X

线程锁

当临界资源是用户级资源时,可以使用线程锁实现临界区。通过线程锁我们可以解决线程安全问题,其步骤如下所示:

1.创建全局变量:CRITICAL_SECTION cs;

2.初始化全局变量:InitializeCriticalSection(&cs);

3.实现临界区:进入 → EnterCriticalSection(&cs); 离开 → LeaveCriticalSection(&cs);

我们就可以这样改写之前的售卖物品的代码,在使用全局变量开始前构建并进入临界区,使用完之后离开临界区:

#include <windows.h>
#include<iostream>
CRITICAL_SECTION cs; // 创建全局变量
int countNumber = 10;DWORD WINAPI ThreadProc(LPVOID lpParameter) 
{       EnterCriticalSection(&cs); // 进入临界区,获取令牌while(countNumber > 0) {printf("Thread: %d\n", *((int*)lpParameter));printf("Sell num: %d\n", countNumber);// 售出-1countNumber--;printf("Count: %d\n", countNumber);}LeaveCriticalSection(&cs); // 离开临界区,归还令牌return 0;
}int main(int argc, char* argv[])
{InitializeCriticalSection(&cs); // 使用之前进行初始化int a = 1;HANDLE hThread;hThread = CreateThread(NULL, NULL, ThreadProc, (LPVOID)&a, 0, NULL);int b = 2;HANDLE hThread1;hThread1 = CreateThread(NULL, NULL, ThreadProc, (LPVOID)&b, 0, NULL);CloseHandle(hThread);CloseHandle(hThread1);getchar();return 0;
}

此时我们发现线程是安全的,只有线程1在执行

互斥体

我们在前文学习的线程锁只能控制用户级的内核资源,其只能控制同一个进程的多个线程共享临界资源。内核级资源可以跨进程共享,当有多个进程的线程同时去访问内核级资源时,线程锁显然不合适,这就需要互斥体了。

当进程创建一个已存在的同名的互斥体时,它并不会创建一个新的互斥体,而是会返回已存在的同名的互斥体句柄,这些进程共享同一个互斥体。

当进程创建互斥体失败时,会返回NULL

比如我们把令牌放到进程B的应用层,那么进程A就无法访问。为保证内核级临界资源的安全,我们需要一个能够放在内核中的令牌来控制,而这是互斥体。

接下来我们尝试使用互斥体:

我们创建两个如下代码的进程

#include <windows.h>
#include<iostream>int main(int argc, char* argv[])
{// 创建互斥体(令牌),起始为有信号的状态HANDLE cm = CreateMutex(NULL, FALSE, "XYZ");// 等待获取令牌,即互斥体有信号或该进程是线程拥有者// 当我们设置互斥体起始为无信号时,运行本进程,再运行其他进程,其他进程由于互斥体无信号,所以无法访问临界资源。但本进程由于是该线程的拥有者,因此尽管互斥体无信号,它仍然以后获取临界资源WaitForSingleObject(cm, INFINITE);// 操作资源// 内核资源为屏幕,printf打印输出到屏幕,占有内核资源for (int i = 0; i < 5; i++) {printf("Process: A Thread: B -- %d \n", i);Sleep(1000);}// 释放互斥体(令牌)ReleaseMutex(cm);return 0;
}

运行程序以后,我们发现只有在其中一个进程执行完毕以后,另一个进程才会继续执行

互斥体和线程锁的区别

1.线程锁只能用于单个进程间的线程控制

2.互斥体可以设定等待超时,但线程锁不能

3.当线程意外结束,互斥体没有释放时,互斥体可以避免无限等待,另一个线程可以访问临界资源

4.互斥体效率没有线程锁高

事件

事件本身可以做为通知类型来使用,创建事件使用函数CreateEvent,其语法格式如下:

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, // SD 安全属性,包含安全描述符BOOL bManualReset,                       // 事件类型:FALSE表示互斥,TRUE表示通知BOOL bInitialState,                      // 初始信号状态:TRUE为有信号,FALSE为无信号LPCTSTR lpName                           // 事件昵称,只在本进程使用可以不命名
);

接下来我们通过代码观察事件有什么特性

#include <windows.h>
#include<iostream>HANDLE e_event;DWORD WINAPI ThreadProc(LPVOID lpParameter) {// 等待事件WaitForSingleObject(e_event, INFINITE);printf("ThreadProc - running ...\n");getchar();return 0;
}DWORD WINAPI ThreadProcB(LPVOID lpParameter) {// 等待事件WaitForSingleObject(e_event, INFINITE);printf("ThreadProcB - running ...\n");getchar();return 0;
}int main(int argc, char* argv[])
{// 创建事件e_event = CreateEvent(NULL, TRUE, FALSE, NULL);// 创建2个线程HANDLE hThread[2];hThread[0] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);hThread[1] = CreateThread(NULL, NULL, ThreadProcB, NULL, 0, NULL);// 设置事件为已通知,也就是设置为有信号SetEvent(e_event);// 等待两个线程执行结束,销毁内核对象WaitForMultipleObjects(2, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);// 事件类型也是内核对象,所以也需要关闭句柄CloseHandle(e_event);return 0;
}

如下图所示,运行程序以后,两个线程都执行了,而如果是之前我们使用互斥体的话,线程A会先执行,执行结束以后线程B才会执行。

注意:我们在线每个程函数的最后都使用了getchar()用于阻止了线程执行结束,但是两个线程还是都执行了:

images/download/attachments/1015833/image2021-5-28_17-55-17.png

我们修改下创建事件类型为互斥,重新运行程序进行观察:

images/download/attachments/1015833/image2021-5-28_17-53-36.png

我们发现,这次只有一个线程运行了

接下来我们开始了解通知类型的作用,这实际上和WaitForSingleObject函数有关:

images/download/attachments/1015833/image2021-5-28_17-2-34.png

WaitForSingleObject会等待内核对象状态的修改,当事件对象为通知类型时该函数就不会去等待修改对象的状态,当事件对象为互斥类型时该函数就会等待去修改对象的状态。这就是前文程序运行两个结果的原因了

信号量

信号量是一个内核对象,创建信号量使用函数CreateSemaphore:

HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,     //安全属性
LONG lInitialCount,                              //当前资源计数:当前可用资源
LONG lMaximumCount,                              //最大资源数:信号量可以控制的最大资源
LPCTSTR lpName );                                //信号量的名字,只在本进程使用时可以NULL

每个内核对象都有两种状态:未通知状态和已通知状态。以线程为例:当线程正在运行时,它是未通知状态,即无信号。当线程运行完毕时,它是已通知状态,即有信号。

信号量规则:

1.当前资源计数大于0时,信号量处于已通知状态

2.当前资源计数等于0,信号量处于未通知状态

3.lInitialCount >= 0

4.lInitialCount <= lMaximumCount

简单的说:信号量的lMaximumCount表示最大允许线程同时运行的数量,而lInitialCount表示当前允许添加新的线程同时运行的数量。当lInitialCount为0时,表示当前不允许有新的线程运行,当lInitialCount>0时,表示当前允许有新的线程运行。

 当需要递增信号量的当前资源计数时,使用函数ReleaseSemaphore:

BOOL ReleaseSemaphore(HANDLE hSemaphore,       //信号量句柄LONG   lReleaseCount,    //增加个数,必须大于0且不超过最大资源数量LPLONG lpPreviousCount   //返回当前资源数量的原始值,NULL表不需要传出
);

我们可以简单的把信号量理解为一个数,当这个数大于0时,表示可以添加新线程运行,但这个数等于0时表示不可以添加新线程运行。这种说法不恰当但符合信号量的使用

线程同步问题

线程互斥:线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。

线程同步: 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。同步的前提是互斥,其次就是有序,互斥并不代表A线程访问临界资源后就一定是B线程再去访问,也有可能是A线程,这就是属于无序的状态,所以同步就是互斥加上有序

线程同步经典的问题就是生产者和消费者的问题:生产者生产一个物品,将其放进容器里,然后消费者从容器中取物品进行消费,如此循环

互斥体解决同步问题

#include <iostream>
#include <windows.h>// 容器
int container;// 次数
int count = 10;// 互斥体
HANDLE hMutex;// 生产者
DWORD WINAPI ThreadProc(LPVOID lpParameter) {for (int i = 0; i < count; i++) {// 等待互斥体,获取令牌WaitForSingleObject(hMutex, INFINITE);// 获取当前进程IDint threadId = GetCurrentThreadId();// 生产存放进容器container = 1;printf("Thread: %d, Build: %d \n", threadId, container);// 释放令牌ReleaseMutex(hMutex);}return 0;
}// 消费者
DWORD WINAPI ThreadProcB(LPVOID lpParameter) {for (int i = 0; i < count; i++) {// 等待互斥体,获取令牌WaitForSingleObject(hMutex, INFINITE);// 获取当前进程IDint threadId = GetCurrentThreadId();printf("Thread: %d, Consume: %d \n", threadId, container);// 消费container = 0;// 释放令牌ReleaseMutex(hMutex);}return 0;
}int main(int argc, char* argv[])
{// 创建互斥体hMutex = CreateMutex(NULL, FALSE, NULL);// 创建2个线程HANDLE hThread[2];hThread[0] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);hThread[1] = CreateThread(NULL, NULL, ThreadProcB, NULL, 0, NULL);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);CloseHandle(hMutex);return 0;
}

运行结果如下 

images/download/attachments/1015833/image2021-5-28_17-57-34.png

我们发现生产和消费并不是有序进行的,甚至还出现了先消费后生产的情况。因此只依靠互斥体只能达到互斥的目的,而不能达到有序的目的。

接下来我们修改代码使得有序进行

images/download/attachments/1015833/image2021-5-28_18-3-24.png

此时便达成了线程的互斥和有序的关系。但这会引发一个问题:for循环执行了不止10次,过分的占用计算资源

事件解决同步问题

为解决计算资源浪费的问题,我们通过事件去解决

#include <windows.h>// 容器
int container = 0;// 次数
int count = 10;// 事件
HANDLE eventA;
HANDLE eventB;// 生产者
DWORD WINAPI ThreadProc(LPVOID lpParameter) {for (int i = 0; i < count; i++) {// 等待事件,修改事件A状态WaitForSingleObject(eventA, INFINITE);// 获取当前进程IDint threadId = GetCurrentThreadId();// 生产存放进容器container = 1;printf("Thread: %d, Build: %d \n", threadId, container);// 给eventB设置有信号SetEvent(eventB);}return 0;
}// 消费者
DWORD WINAPI ThreadProcB(LPVOID lpParameter) {for (int i = 0; i < count; i++) {// 等待事件,修改事件B状态WaitForSingleObject(eventB, INFINITE);// 获取当前进程IDint threadId = GetCurrentThreadId();printf("Thread: %d, Consume: %d \n", threadId, container);// 消费container = 0;// 给eventA设置有信号SetEvent(eventA);}return 0;
}int main(int argc, char* argv[])
{// 创建事件// 线程同步的前提是互斥// 顺序按照先生产后消费,所以事件A设置信号,事件B需要通过生产者线程来设置信号eventA = CreateEvent(NULL, FALSE, TRUE, NULL);eventB = CreateEvent(NULL, FALSE, FALSE, NULL);// 创建2个线程HANDLE hThread[2];hThread[0] = CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);hThread[1] = CreateThread(NULL, NULL, ThreadProcB, NULL, 0, NULL);WaitForMultipleObjects(2, hThread, TRUE, INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);// 事件类型也是内核对象,所以也需要关闭句柄CloseHandle(eventA);CloseHandle(eventB);return 0;
}

运行结果如下图:

images/download/attachments/1015833/image2021-5-28_18-13-7.png

信号量解决同步问题

#include <iostream>
#include <Windows.h>int g_Count = 0;
HANDLE g_Semaphore;DWORD ThreadCallBack1(LPVOID lpThreadParameter
)
{//根据信号量的数值进行WaitForSingleObjectEx(g_Semaphore, -1, TRUE);//获取信号量,信号量减一for (size_t i = 0; i < 200000; i++){g_Count++;}ReleaseSemaphore(g_Semaphore, 1, NULL);//释放信号量,信号量加一return 0;
}DWORD ThreadCallBack2(LPVOID lpThreadParameter
)
{WaitForSingleObjectEx(g_Semaphore, -1, TRUE);for (size_t i = 0; i < 200000; i++){g_Count++;}ReleaseSemaphore(g_Semaphore, 1, NULL);return 0;}int main()
{g_Semaphore = CreateSemaphore(NULL, 1, 2, L"RSemaphore");//设置信号量HANDLE hThread1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack1, NULL, NULL, NULL);HANDLE hThread2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack2, NULL, NULL, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);std::cout << g_Count << std::endl;system("pause");return 0;
}

原子操作解决同步问题

原子操作比较低能,其只能应用于简单的加减法的线程同步问题

原子操作依赖于CPU总线,其维持一种信号用于阻止其他线程访问同一个内存地址,从而实现解决线程同步问题

#include <iostream>
#include <Windows.h>int g_Count = 0;DWORD ThreadCallBack1(LPVOID lpThreadParameter
)
{for (size_t i = 0; i < 200000; i++){//g_Count++;InterlockedAdd((LONG volatile *)&g_Count, 1);//原子加法}return 0;
}DWORD ThreadCallBack2(LPVOID lpThreadParameter
)
{for (size_t i = 0; i < 200000; i++){//g_Count++;InterlockedAdd((LONG volatile *)&g_Count, 1);}return 0;}int main()
{HANDLE hThread1 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack1, NULL, NULL, NULL);HANDLE hThread2 = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)ThreadCallBack2, NULL, NULL, NULL);WaitForSingleObject(hThread1, INFINITE);WaitForSingleObject(hThread2, INFINITE);std::cout << g_Count << std::endl;system("pause");return 0;
}

相关文章:

Windows系统编程(八)线程同步

线程安全问题 每个线程都有自己独立的堆栈&#xff0c;局部变量是存储在栈中的&#xff0c;这就意味着每个线程都会有一份自己的局部变量&#xff0c;当线程仅仅访问自己的局部变量时就不存在线程安全问题。但是全局变量是存储在全局区的&#xff0c;多线程共享全局变量&#…...

create_react_agent 函数,根据创建的 chat 模型实例和工具列表 tools 构造一个“反应式代理”(react agent)

graph create_react_agent(chat, tools)1. create_react_agent 函数的作用 create_react_agent 是一个工厂函数&#xff0c;它接收两个参数&#xff1a; chat 模型实例&#xff08;这里是 ChatOpenAI 的对象&#xff09;&#xff1a;它负责生成语言模型的回复&#xff0c;也…...

Unity ECS与MonoBehaviour混合架构开发实践指南

一、混合架构设计背景 1. 技术定位差异 ECS&#xff08;Entity Component System&#xff09;&#xff1a;面向数据设计&#xff08;DOD&#xff09;&#xff0c;适用于大规模实体计算&#xff08;如10万单位战斗&#xff09; MonoBehaviour&#xff1a;面向对象设计&#x…...

在Linux中开发OpenGL——检查开发环境对OpenGL ES的支持

由于移动端GPU规模有限&#xff0c;厂商并没有实现完整的OpenGL特性&#xff0c;而是实现了它的子集——OpenGL ES。因此如果需要开发的程序要支持移动端平台&#xff0c;最好使用OpenGL ES开发。 1、 下载支持库、OpenGL ES Demo 1.1、下载PowerVRSDK支持库作为准备&#xff…...

「DataX」数据迁移-IDEA运行DataX方法总结

背景 业务需求希望把Oracle数据库中的数据&#xff0c;迁移至MySql数据库中&#xff0c;因为需要迁移全量和增量的数据&#xff0c;所以希望想用数据迁移工具进行操作。 经过一些调研查询&#xff0c;最终打算使用DataX进行数据的迁移。 DataX简单介绍 DataX 是阿里云 DataW…...

python从入门到精通(二十六):python文件操作之Word全攻略(基于python-docx)

python文件操作之word技巧大全 word技巧基础到高级操作大全A.准备工作1. 安装python-docx库2. 导入库 B.基础操作1. 创建Word文档1.1 创建文档对象1.2 添加word标题1.3 添加word段落1.4 设置段落样式1.5 创建有序列表1.6 创建无序列表1.7添加word分页1.8 添加word图片1.9 添加w…...

STM32 ST-LINK Utility 切换 NRST_MODE 后下载失败问题

在使用 STM32 ST-LINK Utility 烧录时&#xff0c;有需要改变芯片选择复位的时候需要修改 Option Bytes 中的 NRST_MODE 选项&#xff0c;可能会遇见 “Programming error 0x8000200!” 的错误&#xff0c;后面不管是取消读写加密还是复位都不能下载&#xff0c;包括再用 keil …...

【算法】010、合并两个有序链表

【算法】010、合并两个有序链表 文章目录 一、合并两个有序链表1.1 思路1.2 多语言解法 一、合并两个有序链表 1.1 思路 // go package mainimport ("fmt""strconv" )type ListNode struct {Val intNext *ListNode }func (n *ListNode) String() (ans s…...

FreeRTOS任务状态查询

一.任务相关API vTaskList&#xff08;&#xff09;&#xff0c;创建一个表格描述每个任务的详细信息 char biaoge[1000]; //定义一个缓存 vTaskList(biaoge); //将表格存到这缓存中 printf("%s /r/n",biaoge); 1.uxTaskPriorityGet&#xff08;&#xf…...

Django小白级开发入门

1、Django概述 Django是一个开放源代码的Web应用框架&#xff0c;由Python写成。采用了MTV的框架模式&#xff0c;即模型M&#xff0c;视图V和模版T。 Django 框架的核心组件有&#xff1a; 用于创建模型的对象关系映射为最终用户设计较好的管理界面URL 设计设计者友好的模板…...

R语言的基础命令及实例操作

> T & F [1] FALSE > T & T [1] TRUE > T | F [1] TRUE > F | F [1] FALSE > a <- c(T,F,T) > b <- c(F,F,T) > a & b [1] FALSE FALSE TRUE > a | b [1] TRUE FALSE TRUE 在 R 中&#xff0c;大小写是敏感的&#xff0c;也就是说…...

通用信息抽取大模型PP-UIE开源发布,强化零样本学习与长文本抽取能力,全面适配多场景任务

背景与简介 信息抽取&#xff08;information extraction&#xff09;是指&#xff0c;从非结构化或半结构化数据&#xff08;如自然语言文本&#xff09;中自动识别、提取并组织出结构化信息。通常包含多个子任务&#xff0c;例如&#xff1a;命名实体识别&#xff08;NER&am…...

【玩转正则表达式】将正则表达式中的分组(group)与替换进行结合使用

在文本处理和数据分析领域&#xff0c;正则表达式&#xff08;Regular Expressions&#xff0c;简称regex&#xff09;是一种功能强大的工具。它不仅能够帮助我们匹配和搜索字符串中的特定模式&#xff0c;还能通过分组&#xff08;Grouping&#xff09;和替换&#xff08;Subs…...

Kotlin和Java区别

哈哈哈&#xff0c;前段时间&#xff0c;面试的时候&#xff0c;突然问到我Kotlin和Java的区别&#xff0c;一下子把我问懵逼了&#xff0c;确实没遇到问这个的&#xff0c;想了下&#xff0c;说了下Kotlin的编译时空检查机制&#xff0c;代码更简洁&#xff0c;很多封装好的AP…...

大语言模型进化论:从达尔文到AI的启示与展望

文章大纲 引言大语言模型中的“进化论”思想体现遗传变异过度繁殖和生存斗争大模型“过度繁殖”与“生存竞争”机制解析**一、过度繁殖:技术迭代的指数级爆发****二、生存竞争:计算资源的达尔文战场****三、生存竞争胜出关键要素****四、行业竞争格局演化趋势**核心结论自然选…...

Django系列教程(5)——Django模型详解

目录 模型定义小案例 模型的组成 模型的字段 基础字段 关系字段 on_delete删除选项 related_name选项 模型的META选项 模型的方法 标准方法 示例一&#xff1a;自定义方法 示例二&#xff1a;自定义Manager方法 完美的高级Django模型示例 小结 Model (模型) 简而…...

2008-2024年中国手机基站数据/中国移动通信基站数据

2008-2024年中国手机基站数据/中国移动通信基站数据 1、时间&#xff1a;2008-2024年 2、来源&#xff1a;OpenCelliD 3、指标&#xff1a;网络类型、网络代数、移动国家/地区、移动网络代码、区域代码、小区标识、单元标识、坐标经度、坐标纬度、覆盖范围、测量样本数、坐标…...

Java在word中动态增加表格行并写入数据

SpringBoot项目中在word中动态增加表格行并写入数据,不废话,直接上配置和代码: 模板内容如下图所示: 模板是一个空word表格即可,模板放在resources下的自定义目录下,如下图示例。 实体类定义如下: @Data @AllArgsConstructor @NoArgsConstructor public class Person …...

记录小白使用 Cursor 开发第一个微信小程序(二):创建项目、编译、预览、发布(250308)

文章目录 记录小白使用 Cursor 开发第一个微信小程序&#xff08;二&#xff09;&#xff1a;创建项目、编译、预览、发布&#xff08;250308&#xff09;一、创建项目1.1 生成提示词1.2 生成代码 二、编译预览2.1 导入项目2.2 编译预览 三、发布3.1 在微信开发者工具进行上传3…...

JavaScript基础-比较运算符

在JavaScript编程中&#xff0c;比较运算符用于比较两个值&#xff0c;并返回一个布尔值&#xff08;true或false&#xff09;&#xff0c;这对于我们进行条件判断和逻辑控制至关重要。掌握这些运算符不仅有助于编写高效的代码&#xff0c;也是处理复杂逻辑的基础。本文将详细介…...

2025 docker安装TiDB数据库

1.确保安装了docker和docker-compose sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-composesudo chmod x /usr/local/bin/docker-compose2.编写 Docker Compose 文…...

【大学生体质】智能 AI 旅游推荐平台(Vue+SpringBoot3)-完整部署教程

智能 AI 旅游推荐平台开源文档 项目前端地址 ☀️项目介绍 智能 AI 旅游推荐平台&#xff08;Intelligent AI Travel Recommendation Platform&#xff09;是一个利用 AI 模型和数据分析为用户提供个性化旅游路线推荐、景点评分、旅游攻略分享等功能的综合性系统。该系统融合…...

【定制开发】碰一碰发视频系统定制开发,支持OEM

在短视频营销爆发的2025年&#xff0c;"碰一碰发视频"技术已成为实体商家引流标配。某连锁餐饮品牌通过定制化开发&#xff0c;单月视频发布量突破10万条&#xff0c;获客成本降低80%&#xff01;本文将深入解析该系统的技术架构与开发要点&#xff0c;助你快速搭建高…...

模型的原始输出为什么叫 logits

模型的原始输出为什么叫 logits flyfish 一、Logarithm&#xff08;对数 log&#xff09; 定义&#xff1a;对数是指数运算的逆运算&#xff0c;表示某个数在某个底数下的指数。 公式&#xff1a;若 b x a b^x a bxa&#xff0c;则 log ⁡ b ( a ) x \log_b(a) x logb…...

YOLOv8改进SPFF-LSKA大核可分离核注意力机制

YOLOv8改进------------SPFF-LSKA 1、LSAK.py代码2、添加YAML文件yolov8_SPPF_LSKA.yaml3、添加SPPF_LSKA代码4、ultralytics/nn/modules/__init__.py注册模块5、ultralytics/nn/tasks.py注册模块6、导入yaml文件训练 1、LSAK.py代码 论文 代码 LSKA.py添加到ultralytics/nn/…...

Unity, AssetBundle的一些“隐藏”方法

​ 只分享实战,理论不多说了,网上都烂大街了 在Project View可以通## 标题过输入“b:” 找到所有带assetbundleName的物件 ​​ AssetBundle打包前的查找和管理方法 若需要获取 每个AssetBundle名称对应的所有具体资源文件路径(类似AssetBundle Browser工具的功能),可以…...

分布式存储学习——HBase概述

1.1 HBase概述 1.1.1 理解大数据背景 1.1.2 HBase是什么 1.1.3 HBase与Hadoop的关系 1.1.4 HBase的核心功能模块 1.1.5 HBase的应用场景和经典案例 1.1.6 小结 本文参考于学校《HBase应用于开发》教材 1.1 HBase概述 本节将介绍大数据背景和HBase的基本概念&#xff0c…...

Mysql表的复合查询

1.基本查询 使用scott案列 ----来源csdn&#xff1a; Mysql下-scott用户表的创建_风泊月mysql 员工表-CSDN博客 案列1&#xff1a;查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大小的J 查询雇员&#xff0c;从emp表中查询&#xff0c;s…...

RAG技术的PDF智能问答系统

关键要点 系统基于RAG&#xff08;检索增强生成&#xff09;技术&#xff0c;允许用户上传PDF并进行智能问答。 使用Ollama的deepseek-r1模型和FAISS向量数据库&#xff0c;支持普通对话和基于PDF的问答模式。 提供简洁的Web界面&#xff0c;支持文件拖拽上传和多轮对话。 研…...

【Java基础-52】Java中URL类的openConnection()方法:原理与应用场景

在Java编程中&#xff0c;java.net.URL类是一个非常重要的类&#xff0c;用于表示统一资源定位符&#xff08;URL&#xff09;。通过URL类&#xff0c;我们可以方便地访问网络资源。其中&#xff0c;openConnection()方法是URL类中一个非常强大的方法&#xff0c;它允许我们与U…...

android为第三方提供部分系统接口

文章目录 Settings - 亮灭屏Settings - 恢复出厂设置Settings - 数字锁屏/解锁Settings - 设置系统时间PackageInstaller - 安装/卸载第三方应用摘要:本文对系统模块进行改造,提供广播等形式的接口对外提供无法直接调用的系统级别接口,实现部分功能的集合。如果是广播形式,…...

C#控制台应用程序学习——3.8

一、语言概述 1、平台相关性 C# 主要运行在.NET 平台上。.NET 提供了一个庞大的类库&#xff0c;C# 程序可以方便地调用这些类库来实现各种功能&#xff0c;如文件操作、数据库访问、网络通信等。 2、语法风格 C# 的语法与 C、C 和 Java 有一定的相似性。例如&#xff0c;它使用…...

钣金加工行业数字化转型MES方案

一、 行业痛点&#xff1a;钣金加工行业普遍面临以下挑战&#xff1a; 订单多样化、小批量、定制化需求增多&#xff1a;传统生产模式难以适应快速变化的市场需求。 生产流程复杂、工序繁多&#xff1a;涉及切割、折弯、焊接、表面处理等多个环节&#xff0c;协同效率低。 生产…...

算法-回溯算法总结

回溯与递归的区别 回溯的本质是穷举&#xff0c;回溯一定代表有递归 递归就一直往深处递归就好了&#xff0c;但是回溯还伴随着递归结束之后的”回溯操作“&#xff0c;例如递归中处理的1&#xff0c;在回溯中要-1。 回溯的算法思路 一般都是返回void,参数不能一下子全部想定…...

ORACLE 执行查询语句慢(不走对应索引)

1. 索引未被创建或未正确创建 确保为查询中涉及的列创建了索引。例如&#xff0c;如果你经常需要按column_name列进行查询&#xff0c;确保已经为该列创建了索引,索引创建语句 CREATE INDEX idx_column_name ON table_name(column_name); 2、索引不可用 原因:索引可能被标记为不…...

零售交易流程相关知识(top-down拆解)

引入 关于POS机交易时的后台数据交互 模块之间数据交换&#xff0c;都可以能被窃取或篡改。由此引入加密、解密机制和签名、验签机制 经典的加密、解密机制&#xff1a; 对称加密&#xff1a;DES\ TDES\ AES\ RC4 非对称加密&#xff1a;RSA\ DSA\ ECC 经典的签名、验签…...

在人工智能软件的帮助下学习编程实例

1 引言 本文记录在人工智能软件的帮助下学习一种全新的编程环境的实例&#xff0c;之所以提人工智能软件而不是单指DeepSeek&#xff0c;一方面DeepSeek太火了&#xff0c;经常服务器繁忙&#xff0c;用本机本地部署的最多运行70b模型&#xff0c;又似乎稍差。另一方面也作为一…...

C语言_数据结构总结5:顺序栈

纯C语言代码&#xff0c;不涉及C 想了解链式栈的实现&#xff0c;欢迎查看这篇文章&#xff1a;C语言_数据结构总结6&#xff1a;链式栈-CSDN博客 这里分享插入一下个人觉得很有用的习惯&#xff1a; 1. 就是遇到代码哪里不理解的&#xff0c;你就问豆包&#xff0c;C知道&a…...

c++ 游戏入门指南

在C++游戏开发中,你需要结合高性能编程、图形学、数学和游戏设计等多方面的知识。以下是C++游戏开发的核心步骤、工具和资源整理,帮助你从入门到进阶: 1. 开发环境搭建 编译器:MSVC(Visual Studio)、GCC、Clang。IDE:Visual Studio(Windows)、JetBrains CLion(跨平台…...

npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。

1、在 vscode 终端执行 get-ExecutionPolicy 返回 Restricted 状态是禁止的 返回 RemoteSigned 状态是可正常执行npm命令 2、更改状态 set-ExecutionPolicy RemoteSigned 如果提示需要管理员权限&#xff0c;可加参数运行 Set-ExecutionPolicy -Scope CurrentUser RemoteSi…...

STM32项目分享:智能家居语音系统(ASRPRO版)

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; STM32智能家居语音系统&#xff08;ASRPRO版&am…...

vue2实现组件库的自动按需引入,unplugin-auto-import,unplugin-vue-components

1.使用ant-design-vue或者element-ui时&#xff0c;如何每个组件都去import导入组件&#xff0c;大大降低了开发效率&#xff0c;如果全局一次性注册会增加项目体积&#xff0c;那么如何实现既不局部引入&#xff0c;也不全局注册&#xff1f; 2.在element-plus官网看到有说明…...

前端安全面试题汇总及参考答案

目录 简述 XSS 攻击的原理及三种常见类型(存储型、反射型、DOM 型) 如何在前端防御 XSS 攻击?列举编码、过滤、CSP 策略的具体实现方式 富文本编辑器场景下如何安全处理用户输入的 HTML 内容? 如何通过 HttpOnly 属性增强 Cookie 安全性?它与 XSS 防御的关系是什么? …...

《打造视频同步字幕播放网页:从0到1的技术指南》

《打造视频同步字幕播放网页&#xff1a;从0到1的技术指南》 为什么要制作视频同步字幕播放网页 在数字化信息飞速传播的当下&#xff0c;视频已然成为内容输出与获取的核心载体&#xff0c;其在教育、娱乐、宣传推广等诸多领域发挥着举足轻重的作用 。制作一个视频同步字幕播…...

Redis 篇

一、数据结构 二、持久化方式 Redis 提供了两种主要的持久化方式&#xff0c;分别是 RDB&#xff08;Redis Database&#xff09;和 AOF&#xff08;Append Only File&#xff09;&#xff0c;此外&#xff0c;还可以同时使用这两种方式以增强数据安全性&#xff0c;以下为你…...

STM32常见外设的驱动示例和代码解析

以下是针对STM32常见外设的驱动示例和代码解析,基于HAL库实现,适用于大多数STM32系列(如F1/F4/H7等),可根据具体型号调整引脚和时钟配置。 1. GPIO驱动 应用场景:控制LED、按键检测、继电器开关等。 示例代码: // 初始化LED(推挽输出) void LED_Init(void) {GPIO_In…...

docker-compose Install reranker(fastgpt支持) GPU模式

前言BGE-重新排名器 与 embedding 模型不同,reranker 或 cross-encoder 使用 question 和 document 作为输入,直接输出相似性而不是 embedding。 为了平衡准确性和时间成本,cross-encoder 被广泛用于对其他简单模型检索到的前 k 个文档进行重新排序。 例如,使用 bge 嵌入模…...

【计算机网络入门】应用层

目录 1.网络应用模型 1.1 C/S模型&#xff08;客户端服务器模型&#xff09; 1.2 P2P模型&#xff08;对等模型&#xff09; 2. DNS系统 2.1 域名 2.2 域名解析流程 3. FTP文件传输协议 4. 电子邮件系统 4.1 SMTP协议 4.2 pop3协议 4.3 IMAP协议 4.4 基于万维网的电…...

/***************************所有笔记汇总目录***************************/

文章分类目录 STM32CubeMX 01、STM32CubeMX——定时器&#xff08;普通模式和PWM模式&#xff09; 02、STM32CubeMX——串口&#xff08;HAL库&#xff09; 03、STM32CubeMX——(uart_IAP串口)简单示例 04、STM32CubeMX——ADC采集单通道&#xff0c;多通道&#xff0c;内部…...

mysql虚拟列

目录 1. 关于虚拟列 2. 虚拟列分类 3. 虚拟列使用 3.1 创建虚拟列 3.2 为虚拟列添加索引 3.3 验证虚拟列索引是否生效 3.4 删除虚拟列 1. 关于虚拟列 MySQL 5.7 版本引入了虚拟列&#xff08;也称为生成列&#xff09;的功能&#xff0c;这是一种在物理表上定义的虚拟列…...