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

一个用libcurl多线程下载断言错误问题的排查

某数据下载程序,相同版本的代码,在64位系统中运行正常,但在32位系统中概率性出现断言错误。一旦出现,程序无法正常继续,即使重启亦不行。从年前会上领导提出要追到根,跟到底,到年后的今天,经过排查、自测、试点,算是告一段落了。文中没有很难的技术问题,但过程还是值得记录的。本文从后来者角度总结一下解决问题的过程,同时给出相关测试代码。

由于本文没有技术含量,请谨慎按需阅读。

起因

当运维人员把出错的截图发给我时,我回想起1年半前的那个夏天,那天下午,运维人员将同一个错误截图给我,后来回退版本了,再也没出现了。这次,运维人员上报给了领导。会上我也答不出来为什么在32位系统上会报错,而在64位系统中却不行,也回答不了为什么回退版本又可以。下面这个错误,在笔记里躺了很久,现在又要翻出来,一点也没变化:

ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.

这是某个动态库报的断言错误,不是业务程序直接提示的。虚拟机模拟不出问题,现场机器没有gdb,也无法生成coredump,也没有pstrace,只能靠头脑分析排查了。

排查及解决

动态库定位

报错信息关键信息为ath.c_gcry_ath_mutex_lockath_mutex_t

经搜索,得到了一些有用的信息。

在libssh2官方网站上,找到一篇关于FIPS兼容性的帖子FIPS Compliance,提问者的错误是用sftp通过libcurl下载文件时产生的。路线和所遇问题几乎一样,出现断言错误的库为libgcrypt。在stackoverflow网站上找到这个帖子,帖子回答者提到:

Obviously you are using libgcrypt in there, either directly or through some library (liboauth?). Multithreaded use of gcrypt requires initialization, as documented at gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html - either you forgot it, or one of the maintainers of libraries you used did. Check the documentation… –
DevSolar
Dec 13, 2011 at 17:00
The problem was that more than one thread tries to get a http request at the same time. That’s not possible. So I used mutexes to avoid that.

使用libgcrypt时,在多线程中要初始化,至于初始化什么,怎么初始化,谁初始化,由于涉及libcurl->libssh2->libgcrypt等库,路径较深,鞭长莫及,代码人一声叹气。

关于ath.c断言语句的跟踪记录

前面定位到了libgcrypt库,在32位系统上用strings命令查找出错关键字:

 strings /lib/i386-linux-gnu/libgcrypt.so.11.7.0 | grep "*lock =="
*lock == ((ath_mutex_t) 0)
*lock == ((ath_mutex_t) 1)strings /lib/i386-linux-gnu/libgcrypt.so.11.7.0 | grep "_gcry_ath_mutex_lock"
_gcry_ath_mutex_lock

是这个库无疑了。但11.7.0版本找不到ath.c文件。再在64位系统上查:

 strings /lib64/libgcrypt.so.11.8.2 | grep "*lock =="
*lock == ((ath_mutex_t) 0)
*lock == ((ath_mutex_t) 1)strings /lib64/libgcrypt.so.11.8.2 | grep "_gcry_ath_mutex_lock"
_gcry_ath_mutex_lockfind /usr/ -name "libgcrypt*"
/usr/lib64/libgcrypt.so.11.8.2
/usr/lib64/libgcrypt.so.11
/usr/share/doc/libgcrypt-1.5.3

从信息中猜测,可能的版本是1.5.3。下载该版本解压,得到ath.c关键语句:
在这里插入图片描述

第193行,正是这多天魂牵梦萦想看到的语句。

但是,这只是知道了出错的地方而已,还不知道如何出错。

问题定位

从错误信息上看,和锁有关,进而推断和多线程有关。分析业务代码,的确有多线程下载。起初,跟踪线程内部的curl变量,但没有出现越界使用情况,都在相同内部线程完成了,每次下载,都用curl_easy_init初始化,最后用curl_easy_cleanup清理,通过打印跟踪可以确认这一点。

回到业务程序上。既然是多线程下载出错,就将多线程改成单线程,或者加上互斥锁,不让他们同时运行。这份代码比大锤的年龄还大,有一定的历史沉淀,且有较多名称相近的函数,类似于北湖北路,北湖南路,北湖东路等,不好改动。于是加上锁,再测试,没有发现问题。

解决方法

但依然没有找到原因。

访问libcurl官方示例页面,找到多线程例子,但参考价值不大,于是搭建sftp服务器,写了测试程序。经摸索,发现在一线程下载过程的同时另一线程也下载,则必然出错。开始时,下载的文件小,很快下载完毕,下载大文件时问题即刻暴露了。

回查业务代码,在两线程启动时,人工加了10秒的延时,当数据量较多大了,前一线程未下载完毕,后一线程启动,因此报错了。之前相安无事,应该是两线程下载的数据量并非都大,前一线程较快完成了下载。

但无论如何,在两线程之间加锁,的确能解决问题。

反馈

在试点跑了3天,暂时没有收到问题反馈。应该大概的确解决了这个问题。

小结

至于为何libcurl无法多线程下载,为何偏偏在32位系统上出现,其实还没有找到根本问题所在。多方测试,64位系统的确未发现有。于是在业务代码中通过宏定义限定只在32位系统才加锁。

附:自测程序

源码:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <curl/curl.h>#define DOWNLOAD_LOCKstatic pthread_mutex_t connlock;void init_locks(void)
{pthread_mutex_init(&connlock, NULL);
}void kill_locks(void)
{pthread_mutex_destroy(&connlock);
}void my_locks(void)
{
#ifdef DOWNLOAD_LOCKpthread_mutex_lock(&connlock);
#endif
}    void my_unlocks(void)
{
#ifdef DOWNLOAD_LOCKpthread_mutex_unlock(&connlock);
#endif
}//lldebug LoginStr aftp:123456 RemoteFile: sftp://192.168.168.88/DataStorary/Server/MyData/BigFile.dat LocalFile: /tmp/data/download/BigFile.datstatic size_t my_write(void *buffer, size_t size, size_t nmemb, void *stream)
{/* not interested in the downloaded bytes, return the size */ (void)buffer;  /* unused */ (void)stream; /* unused */ return (size_t)(size * nmemb);
}void downloadFile(CURL *curl, const char *LoginStr, const char * RemoteFile, const char *LocalFile)
{//CURL *curl = InitCurl();if(curl == NULL){return;}curl_easy_setopt(curl, CURLOPT_USERPWD, LoginStr);curl_easy_setopt(curl, CURLOPT_URL, RemoteFile);curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write);printf("lldebug %s().%d before curl_easy_perform ptr: %p\n", __func__, __LINE__, curl);CURLcode code = curl_easy_perform(curl);if(CURLE_OK != code){printf("curl_easy_perform failed, %d: %s\n", code, curl_easy_strerror(code));}
}static void *download_1(void *url)
{CURL *curl;const char* loginStr = "aftp:123456";//const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/smallfile.txt";const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/BigFile.dat";const char* lfile = "/tmp/file_1.txt";my_locks();printf("lldebug %s().%d begin++++++++++++++++++\n", __func__, __LINE__);curl = curl_easy_init();printf("lldebug %s().%d >>>>>>>> init curl ptr: %p\n", __func__, __LINE__, curl);downloadFile(curl, loginStr, rfile, lfile);printf("lldebug %s().%d <<<<<<<< download done curl ptr: %p\n", __func__, __LINE__, curl);curl_easy_cleanup(curl);printf("lldebug %s().%d end++++++++++++++++++\n", __func__, __LINE__);my_unlocks();
}static void *download_2(void *url)
{CURL *curl;const char* loginStr = "aftp:123456";const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/smallfile.txt";const char* lfile = "/tmp/file_2.txt";my_locks();printf("lldebug %s().%d begin------------------\n", __func__, __LINE__);curl = curl_easy_init();printf("lldebug %s().%d >>>>>>>> init curl ptr: %p\n", __func__, __LINE__, curl);downloadFile(curl, loginStr, rfile, lfile);printf("lldebug %s().%d <<<<<<<< download done curl ptr: %p\n", __func__, __LINE__, curl);curl_easy_cleanup(curl);printf("lldebug %s().%d end------------------\n", __func__, __LINE__);my_unlocks();
}int main(int argc, char **argv)
{pthread_t tid1;pthread_t tid2;init_locks();curl_global_init(CURL_GLOBAL_ALL);pthread_create(&tid1, NULL, download_1, NULL);usleep(1000);pthread_create(&tid2, NULL, download_2, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);kill_locks();curl_global_cleanup();return 0;
}

编译:

g++ test_thread.cpp  -I/usr/local/curl/include -I/usr/include -lcurl

加锁情况下运行结果:

lldebug download_1().78 begin++++++++++++++++++
lldebug download_1().82 >>>>>>>> init curl ptr: 0x9216340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9216340
lldebug download_1().86 <<<<<<<< download done curl ptr: 0x9216340
lldebug download_1().91 end++++++++++++++++++
lldebug download_2().105 begin------------------
lldebug download_2().110 >>>>>>>> init curl ptr: 0x9216340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9216340
lldebug download_2().114 <<<<<<<< download done curl ptr: 0x9216340
lldebug download_2().118 end------------------

不加锁情况下运行结果:

lldebug download_1().78 begin++++++++++++++++++
lldebug download_1().82 >>>>>>>> init curl ptr: 0x9d37340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9d37340
lldebug download_2().105 begin------------------
lldebug download_2().110 >>>>>>>> init curl ptr: 0x9d4de18
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9d4de18
a.out: ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.
Aborted

用gdb调试过程:

(gdb) r
Starting program: /home/latelee/libcurl_test/a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".
[New Thread 0xb769eb70 (LWP 7680)]
lldebug download_1().101 begin++++++++++++++++++
lldebug download_1().105 >>>>>>>> init curl ptr: 0x805a340
lldebug downloadFile().80 before curl_easy_perform ptr: 0x805a340
[New Thread 0xb6e9db70 (LWP 7681)]
lldebug download_2().128 begin------------------
lldebug download_2().133 >>>>>>>> init curl ptr: 0x8070e18
lldebug downloadFile().80 before curl_easy_perform ptr: 0x8070e18
a.out: ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.Program received signal SIGABRT, Aborted.
[Switching to Thread 0xb6e9db70 (LWP 7681)]
0xb7fe1424 in __kernel_vsyscall ()
(gdb) bt
#0  0xb7fe1424 in __kernel_vsyscall ()
#1  0xb7cf5941 in *__GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0xb7cf8d72 in *__GI_abort () at abort.c:92
#3  0xb7ceeb58 in *__GI___assert_fail (assertion=0xb7938626 "*lock == ((ath_mutex_t) 0)", file=0xb7938620 "ath.c", line=193, function=0xb7938674 "_gcry_ath_mutex_lock") at assert.c:81
#4  0xb78e9605 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#5  0xb792b02d in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#6  0xb792c889 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#7  0xb792ae7d in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#8  0xb78f82d9 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#9  0xb78f8a14 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#10 0xb78e078c in gcry_md_open () from /lib/i386-linux-gnu/libgcrypt.so.11
#11 0xb7c603b6 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#12 0xb7c6f154 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#13 0xb7c5a2c8 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#14 0xb7c67108 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#15 0xb7c67489 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#16 0xb7c680db in libssh2_sftp_init () from /usr/lib/i386-linux-gnu/libssh2.so.1
#17 0xb7fa0688 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#18 0xb7fa3f63 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#19 0xb7fa4595 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#20 0xb7f80380 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#21 0xb7f8067a in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#22 0xb7f80732 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#23 0xb7f8d255 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#24 0xb7f8e033 in curl_easy_perform () from /usr/lib/i386-linux-gnu/libcurl.so.4
#25 0x08048acf in downloadFile(void*, char const*, char const*, char const*) ()
#26 0x08048c60 in download_2(void*) ()
#27 0xb7cb7c39 in start_thread (arg=0xb6e9db70) at pthread_create.c:304
#28 0xb7da1d4e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130
(gdb) 

相关文章:

一个用libcurl多线程下载断言错误问题的排查

某数据下载程序&#xff0c;相同版本的代码&#xff0c;在64位系统中运行正常&#xff0c;但在32位系统中概率性出现断言错误。一旦出现&#xff0c;程序无法正常继续&#xff0c;即使重启亦不行。从年前会上领导提出要追到根&#xff0c;跟到底&#xff0c;到年后的今天&#…...

Docker的安装及MySQL的部署(CentOS版)

目录 1 前言 2 Docker安装步骤 2.1 卸载可能存在的旧版Docker 2.2 配置Docker的yum库 2.2.1 安装yum工具 2.2.2 配置Docker的yum源 2.3 安装Docker 2.4 启动和校验 2.5 配置镜像加速(使用阿里云) 2.5.1 进入控制台 2.5.2 进入容器镜像服务 2.5.3 获取指令并粘贴到…...

css 背景图片居中显示

background 简写 background: #ffffff url(https://profile-avatar.csdnimg.cn/b9abdd57de464582860bf8ade52373b6_misnice.jpg) center center / 100% no-repeat;效果如图&#xff1a;...

Python编程-如何轻松开启一个Web服务?

目录 前言 Flask&#xff1a;轻量级 Django&#xff1a;高级 Bottle&#xff1a;轻量级 Pyramid&#xff1a;灵活且可扩展 Tornado&#xff1a;可扩展、非阻塞 CherryPy&#xff1a;微型 轻量级的Web框架的选择 前言 在Python中开启一个Web服务有多种方法&#xff0c;选…...

鸡肋的Git

1.前言 对于大多数开发人员来说&#xff0c;我们大多数在学习或者工作过程中只关注核心部分&#xff0c;比如说学习Java&#xff0c;可能对于大多数人而言一开始都是从Java基础学起&#xff0c;然后408&#xff0c;Spring&#xff0c;中间件等&#xff0c;当你发现很多高深的技…...

iOS 中的 UITextField 如何设置才能只输入数字和小数点?

刚接触 iOS不久&#xff0c;接到一个iOS项目&#xff0c;其中有一个需求就是在一个 UITextField中只能输入数字和小数点&#xff0c;这个需求在Android中非常容易&#xff0c;只需要设置 <EditTextandroid:id"id/id_et_price"android:layout_width"match_par…...

阿珊详解Vue Router的守卫机制

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…...

python淘宝网页爬虫数据保存到 csv和mysql(selenium)

数据库连接设置&#xff08;表和字段要提前在数据库中建好&#xff09; # 数据库中要插入的表 MYSQL_TABLE goods# MySQL 数据库连接配置,根据自己的本地数据库修改 db_config {host: localhost,port: 3306,user: root,password: ma*****6,database: may2024,charset: utf8mb…...

云计算中仲裁节点和仲裁可用区介绍

在云计算中&#xff0c;仲裁是指当多个节点或实例之间发生争议或冲突时&#xff0c;由一个独立的第三方机构或节点来解决争议或冲突的过程。仲裁通常用于解决云计算中的资源分配、数据一致性、服务质量等问题。 仲裁的作用主要有以下几点&#xff1a; 1. 保证资源分配的公平性…...

Python的requests库发送HTTPS请求时,SSL证书验证的流程

建立连接&#xff1a;当你使用requests库发送HTTPS请求时&#xff0c;它会尝试与目标服务器建立安全的SSL连接。 获取服务器SSL证书&#xff1a;服务器会将自己的SSL证书发送给客户端&#xff08;即你的请求&#xff09;。 验证证书&#xff1a;requests库会验证服务器返回的…...

基于 HBase Phoenix 构建实时数仓(3)—— Phoenix 安装

目录 一、主机规划 二、Phoenix 安装 1. 解压、配置环境 2. 将 phoenix-server-hbase-2.5-5.1.3.jar 文件复制到 HBase 的 lib 目录中 3. 重启 HBase 集群 4. 安装验证 &#xff08;1&#xff09;连接 HBase &#xff08;2&#xff09;视图映射 &#xff08;3&#xf…...

Python IDE

Python IDE 本文为大家推荐几款款不错的 Python IDE&#xff08;集成开发环境&#xff09;&#xff0c;比较推荐 PyCharm&#xff0c;当然你可以根据自己的喜好来选择适合自己的 Python IDE。 PyCharm PyCharm 是由 JetBrains 打造的一款 Python IDE。 PyCharm 具备一般 Pyt…...

Vue3和Vue2的区别

Vue 3 是 Vue.js 的下一个主要版本&#xff0c;它引入了许多新特性和优化&#xff0c;与 Vue 2 相比有一些显著的区别。以下是一些主要的区别&#xff1a; 性能提升: Vue 3 提供了更快的虚拟 DOM&#xff0c;减少了渲染和更新时间。使用 Proxy 重写了响应式系统&#xff0c;使…...

【内推】新风口-大模型独角兽公司minimax

先上内推链接&#xff1a; MiniMax社招内推码: AK3XEJ6 投递链接: https://vrfi1sk8a0.jobs.feishu.cn/s/iFY5WFgE 岗位&#xff1a;前端、后端、算法&#xff0c;基础架构都有&#xff0c;大量hc 公司介绍&#xff1a; 国内同时拥有文本、语音、视觉三种基础大模型能力的创业…...

基于单片机的水平角度仪系统设计

目 录 摘 要 I Abstract II 引 言 1 1控制系统设计 3 1.1系统方案设计 3 1.2系统工作原理 4 2硬件设计 6 2.1单片机 6 2.1.1单片机最小系统 6 2.1.2 STC89C52单片机的性能 7 2.2角度采集电路 8 2.2.1 ADXL345传感器的工作原理 9 2.2.2 ADXL345传感器倾角测量的原理 9 2.2.3 AD…...

Haproxy 负载均衡集群

一. Haproxy 1. Haproxy 介绍 HAProxy 是法国开发者威利塔罗 (Willy Tarreau) 在2000年使用C语言开发的一个开源软件&#xff0c;是一款具备高并发(一万以上)、高性能的TCP和HTTP负载均衡器&#xff0c;支持基于cookie的持久性&#xff0c;自动故障切换&#xff0c;支持正则…...

微服务知识03

1、ES搜索引擎,高性能的分布式搜索引擎,底层基于Lucene 主要用于应用程序中的搜索系统 日志收集 2、基础概念 3、ES处理流程 5、下载中文分词器 Releases infinilabs/analysis-ik GitHub 6、分词模式 最细粒度拆分、智能分词 7、Elaticsearch配置流程 (1)把文件拖进…...

JPEG照片被误删除如何恢复?学会这个方法就够了

JPG/JPEG是一种后缀名为“.jpg”或“.jpeg”的图形格式。它是存储照片图像的常用格式&#xff0c;因此我们可以使用数码相机、手机或其他设备来获取大量的JPG/JPEG文件。有时&#xff0c;我们会遇到由于意外删除、格式化驱动器或其他未知原因导致 JPEG 文件丢失的情况。无论哪种…...

红黑树的学习

红黑树 红黑树出自一种平衡的二叉查找树&#xff0c;是计算机科学中中用到的一种数据结构 1972年出现&#xff0c;当时被称之为平衡二叉B树。后来&#xff0c;1978年被修改为如今的红黑树 他是一种特殊的二叉查找树&#xff0c;红黑树的每一个节点上都有存储表示节点的颜色 …...

C# OpenCvSharp DNN FreeYOLO 人脸检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 人脸检测 效果 模型信息 Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- Outp…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖

在Vuzix M400 AR智能眼镜的助力下&#xff0c;卢森堡罗伯特舒曼医院&#xff08;the Robert Schuman Hospitals, HRS&#xff09;凭借在无菌制剂生产流程中引入增强现实技术&#xff08;AR&#xff09;创新项目&#xff0c;荣获了2024年6月7日由卢森堡医院药剂师协会&#xff0…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成

一个面向 Java 开发者的 Sring-Ai 示例工程项目&#xff0c;该项目是一个 Spring AI 快速入门的样例工程项目&#xff0c;旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计&#xff0c;每个模块都专注于特定的功能领域&#xff0c;便于学习和…...