【iOS】GCD
参考文章:GCD函数和队列原理探索
之前写项目的时候,进行耗时的网络请求使用GCD处理过异步请求,但对一些概念都很模糊,这次就来系统学习一下GCD相关
相关概念
什么是GCD?
Grand Center Dispatch简称GCD,是苹果公司开发的技术,以优化应用程序支持多核心处理器
GCD的优势:
- GCD 是苹果公司为多核的并⾏运算提出的解决⽅案
- GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)
- GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执⾏什么任务,不需要编写任何线程管理代码
GCD要做的事情就是:GCD将任务添加到队列,并指定执行任务的函数
「任务」
任务: 执行操作,就是在线程中执行的那段代码。在GCD中是放在Block中的。执行任务有两种方式:
- 同步执行(sync):
- 必须等待当前语句执⾏完毕,才会执⾏下⼀条语句
- 不会开启线程
- 只能在当前线程中执行任务,不具备开启新线程的能力
- 异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
- 可以在新的线程中执行任务,具备开启新线程的能力
- 异步是多线程的代名词
注意:异步执行(async) 虽然具有开启新线程的能力,但是并不一定开启新线程,这跟任务指定的队列类型有关
两者主要的区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力
「队列」
队列(Dispatch Queue): 这里的队列指执行任务的等待队列,即用来存放任务的队列。每读取一个任务,则从队列中释放一个任务,参考下图:

队列分为两种:串行队列和并发队列。不同的队列中,任务排列的方式是不一样的,任务通过队列的调度,由线程池安排的线程来执行
两者都符合FIFO(先进先出)的原则:
- 串行队列(Serial Dispatch Queue):每次只有一个任务被执行,让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
- 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列的并发功能只有在异步(dispatch_async)方法下才有效
两者的主要区别是:执行顺序不同,以及开启线程数不同。参考下图:

创建队列:
// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);// 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
MRC下,生成的队列需由程序员负责持有或释放,通过以下两个函数的引用计数来管理内存:
dispatch_retain(serialQueue);
dispatch_release(serialQueue);
队列与函数组合
队列用来调用任务,函数用来执行任务,那么队列和函数不同的配合会有怎样的运行效果呢?
- 同步函数串行队列
- 不会开启线程,在当前线程中执行任务
- 任务串行执行,任务一个接着一个执行
- 会产生阻塞
- 同步函数并发队列
- 不会开启线程,在当前线程中执行任务
- 任务一个接着一个执行
- 异步函数串行队列
- 会开启一个线程
- 任务一个接着一个执行
- 异步函数并发队列
- 开启线程,在当前线程执行任务
- 任务异步执行,没有顺序,CPU调度有关
GCD中的队列
主队列
-
The main queue:系统自带的一个队列,放到这个队列中的代码会被系统分配到主线程中执行。Main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列, 交至其中的任务顺序执行(一个任务执行完毕后,再执行下一个任务)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局队列
-
Global queues:整个应用程序存在4个全局队列(系统已经创建好,只需获得即可):高、中(默认)、后台三个优先级队列,可以调用dispatch_get_global_queue函数传入有下级来访问队列。全局队列是并行队列,可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步(dispatch_async)函数下才有效
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_queue_t globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
自定义队列
- 用户自己创建队列:dispatch_queue_create创建的队列可以是串行的,也可以是并行的,因为系统已经给我们供了并行、串行队列,所以一般情况下我们不再需要在创建自己的队列。用户创建的队列可以有任意多个
注意: 分线程不能刷新UI,刷新UI只能在主线程。如果每个线程都刷新UI,将会很容易造成UI冲突,会出现不同步的情况,所以只有主线程中能刷新UI系统是为了降低编程的复杂程度,最大程度的避免冲突
相关案例分析
耗时性
任务是耗时的,不同函数只要执行任务,都会耗时
异步函数会开启线程,执行的耗时相对较少,在实际开发中,异步可以用来解决并发、多线程等问题
六种情况示例
主队列添加同步任务
在当前的main队列中添加一个任务,并同步执行该任务:
void mainSyncTest(void) {/*主队列同步不会开启线程会崩溃!*/NSLog(@"start");// dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);dispatch_sync(serialQueue, ^{NSLog(@"a");});NSLog(@"b");
}
在当前流程中,默认队列就是主队列,也是一个串行队列,任务执行顺序应为:

而到了第二步的块任务,会向当前的主队列中添加一个任务NSLog(@"a");,因为主队列是一个串行队列,现在要执行b,必须要等a(任务块)执行完成,而a又必须等b执行完成,产生了相互等待问题,造成了死锁!见下图:

运行结果就是崩溃:

解决办法就是将主队列改成自定义的串行队列或并发队列:
NSLog(@"start");// dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{NSLog(@"a");
});NSLog(@"b");
运行结果:

主队列添加异步任务
void mainAyncTest(void) {NSLog(@"start");dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);dispatch_async(serialQueue, ^{NSLog(@"a");});NSLog(@"b");
}
主队列添加异步任务不会阻塞,不会崩溃
运行结果:

并发队列添加异步任务
void concurrentAsyncTest(void) {dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);NSLog(@"1 --- %@", [NSThread currentThread]);dispatch_async(concurrentQueue, ^{NSLog(@"2 --- %@", [NSThread currentThread]);dispatch_async(concurrentQueue, ^{NSLog(@"3 --- %@", [NSThread currentThread]);});NSLog(@"4 --- %@", [NSThread currentThread]);});NSLog(@"5 --- %@", [NSThread currentThread]);
}
并发队列,通道比较宽,不会导致任务的阻塞
每个任务复杂度基本一致,异步不会堵塞主线程,dispatch_async会开启一个新的线程去执行其中的任务块
运行结果:

并发队列添加同步任务
void concurrentSyncTest(void) {dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);NSLog(@"1 --- %@", [NSThread currentThread]);dispatch_sync(concurrentQueue, ^{NSLog(@"2 --- %@", [NSThread currentThread]);dispatch_sync(concurrentQueue, ^{NSLog(@"3 --- %@", [NSThread currentThread]);});NSLog(@"4 --- %@", [NSThread currentThread]);});NSLog(@"5 --- %@", [NSThread currentThread]);
}
因为并发队列,所以不会导致队列任务的阻塞,同时因为是同步执行,所以不会开启新的线程,按照顺序去执行流:

串行队列添加异步任务
void serialAsyncTest(void) {// 串行队列NSLog(@"start --- %@", [NSThread currentThread]);dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);for (int i = 0; i < 5; ++i) {dispatch_async(serialQueue, ^{NSLog(@"%d --- %@", i, [NSThread currentThread]);});}NSLog(@"hello queue");NSLog(@"end --- %@", [NSThread currentThread]);
}
运行结果:

串行队列添加异步任务,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行
另一方面可以看出,所有任务是在打印的start和end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行
串行队列添加同步任务
void serialSyncTest(void) {NSLog(@"start --- %@", [NSThread currentThread]);dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);for (int i = 0; i < 5; ++i) {dispatch_sync(serialQueue, ^{NSLog(@"%d --- %@", i, [NSThread currentThread]);});}NSLog(@"hello queue");NSLog(@"end --- %@", [NSThread currentThread]);
}
运行结果:

串行队列同步执行任务,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行
同时我们还可以看到,所有任务都在打印的start和end之间,这说明任务是添加到队列中马上执行的
串行队列添加同步和异步任务混合
void serialSyncAndAsyncTest(void) {NSLog(@"start --- %@", [NSThread currentThread]);dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);dispatch_async(serialQueue, ^{NSLog(@"a --- %@", [NSThread currentThread]);dispatch_sync(serialQueue, ^{NSLog(@"b --- %@", [NSThread currentThread]);});NSLog(@"d --- %@", [NSThread currentThread]);});NSLog(@"hello queue");NSLog(@"end --- %@", [NSThread currentThread]);
}
在异步添加的线程中,情况类似主队列添加同步函数,b(任务块)和d相互等待了,导致死锁!
运行结果:

并发队列多任务
void concurrentSyncAndAsyncTest(void) {dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue, ^{NSLog(@"1");});dispatch_async(concurrentQueue, ^{NSLog(@"2");});dispatch_sync(concurrentQueue, ^{NSLog(@"3");});NSLog(@"0");dispatch_async(concurrentQueue, ^{NSLog(@"7");});dispatch_async(concurrentQueue, ^{NSLog(@"8");});dispatch_async(concurrentQueue, ^{NSLog(@"9");});
}
// 3 0 1 2 7 9 8
// 3 0 1 7 9 8 2
// 3 0 1 2 7 8 9
// 3 1 0 2 7 8 9
// 3 2 1 0 7 8 9
// 3 1 2 0 7 9 8
结果分析:
- 主队列共10个任务
- 1、2、3的顺序不确定
- 3、0是同步任务,所以3一定在
0前面 - 7、8、9一定在
0后面
GCD线程间通信
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯
void communicateAmongThread(void) {dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i = 0; i < 6; ++i) {NSLog(@"1 --- %@", [NSThread currentThread]);}// 回到主线程dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"2 --- %@", [NSThread currentThread]);});});
}
运行结果:

可以看到在其他线程中先执行操作,执行完了之后回到主线程执行主线程的相应操作
GCD的栅栏方法
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏
这是没添加栅栏函数之前的结果,顺序不确定:

void barrierFunc(void) {dispatch_queue_t queue = dispatch_queue_create("666", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{NSLog(@"1 --- %@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"2 --- %@", [NSThread currentThread]);});dispatch_barrier_async(queue, ^{NSLog(@"----barrier-----%@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"3 --- %@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"4 --- %@", [NSThread currentThread]);});
}
栅栏函数可保证异步操作的执行顺序,这里保证了1或2先执行,3或4后执行:

GCD的延时方法
当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法
void delayExec(void) {NSLog(@"run -- 0");dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 3秒后异步执行这里的代码...NSLog(@"run -- 2");});
}
运行结果:

GCD的一次性代码(只执行一次)
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
void onceExec(void) {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{// 只执行一次的代码(这里面是默认线程安全的)NSLog(@"Hhhhhhhh");});
}onceExec();
onceExec();
调用两次只会执行一次:

GCD的队列组
有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。
- 我们可以先把任务放到队列中,然后将队列放入队列组中
- 调用队列组的dispatch_group_notify回到主线程执行操作
void queueGroup(void) {// GCD的队列组dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 执行1个耗时的异步操作int i = 0;while (i < 100) {NSLog(@"1");i++;}});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 执行1个耗时的异步操作int i = 0;while (i < 100) {NSLog(@"2");i++;}});dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 等前面的异步操作都执行完毕后,回到主线程...NSLog(@"3");});
}
运行结果:

dispatch_set_target_queue
这个函数有两个作用:
- 改变队列的优先级
- 防止多个串行队列的并发执行
改变队列的优先级
dispatch_queue_create函数生成的串行队列和并发队列,都使用 与默认优先级的Global Dispatch Queue 相同执行优先级 的线程
void serialBackgroundQueue(void) {// 需求:生成一个后台的串行队列dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);// 第1个参数:需要改变优先级的队列// 第2个参数:目标队列dispatch_set_target_queue(serialQueue, globalQueueBackground);
}
防止多个串行队列的并发执行
如果是将任务追加到3个串行队列中,那么这些任务就会并发执行。因为每个串行队列都会创建一个线程,这些线程会并发执行
如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么这多个串行queue在目标queue上就是同步执行的,不再是并行执行
将串行队列加入指定优先级队列,会按照加入优先级队列的顺序依次执行串行队列
未加入优先级队列:

void abandonSerialQueuesToConcurrent(void) {dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);// dispatch_set_target_queue(queue1, targetQueue);
// dispatch_set_target_queue(queue2, targetQueue);
// dispatch_set_target_queue(queue3, targetQueue);dispatch_async(queue1, ^{NSLog(@"1 in");[NSThread sleepForTimeInterval:3.f];NSLog(@"1 out");});dispatch_async(queue2, ^{NSLog(@"2 in");[NSThread sleepForTimeInterval:2.f];NSLog(@"2 out");});dispatch_async(queue3, ^{NSLog(@"3 in");[NSThread sleepForTimeInterval:1.f];NSLog(@"3 out");});
}
加入后:

dispatch_suspend/dispatch_resume
dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行
挂起指定队列:dispatch_suspend(serialQueue);
恢复指定队列:dispatchp_resume(serialQueue);
void suspendOrResumeQueue(void) {dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);//提交第一个block,延时5秒打印。dispatch_async(queue, ^{sleep(5);NSLog(@"After 5 seconds...");});//提交第二个block,也是延时5秒打印dispatch_async(queue, ^{sleep(5);NSLog(@"After 5 seconds again...");});//延时一秒NSLog(@"sleep 1 second...");sleep(1);//挂起队列NSLog(@"suspend...");dispatch_suspend(queue);//延时10秒NSLog(@"sleep 17 second...");sleep(17);//恢复队列NSLog(@"resume...");dispatch_resume(queue);
}
线程安全
为了保证线程安全,我们之前了解过互斥锁,在书上给了我们dispatch_semaphore方法,我们总结为三点:
-
synchronized加锁,属于互斥锁,当有线程执行锁住的代码的时候,其他线程会进入休眠,需要唤醒后才能继续执行,性能较低
- (void)synchronizedSecurity {dispatch_async(dispatch_get_global_queue(0, 0), ^{// 加锁保证block中执行完成才会执行其他的@synchronized (self) {NSLog(@"1开始");sleep(2);NSLog(@"1结束");}});dispatch_async(dispatch_get_global_queue(0, 0), ^{@synchronized (self) {NSLog(@"2开始");sleep(2);NSLog(@"2结束");}}); } -
信号量semaphore加锁,属于自旋锁,当有线程执行锁住的代码的时候,其他线程会进入死循环的等待,当解锁后会立即执行,性能较高
提供了三种函数:-
dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量(同时有几个线程可以执行,一般是1)
-
dispatch_semaphore_wait:可以使总信号量减1,当信号总量小于0时就会一直等待(阻塞所在线程),否则就可以正常执行,这个放在要执行的代码的前面。
-
dispatch_semaphore_signal:发送一个信号,让信号总量加1,代码执行完成之后使用,使其他线程可以继续执行
void semaphoreSecurity(void) {dispatch_semaphore_t semalook = dispatch_semaphore_create(1);dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);NSLog(@"1开始");sleep(2);NSLog(@"1结束");dispatch_semaphore_signal(semalook);});dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);NSLog(@"2开始");sleep(2);NSLog(@"2结束");dispatch_semaphore_signal(semalook);}); }
-
-
NSLock
void lockSecurity(void) {NSLock *lock = [[NSLock alloc]init];dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(@"1开始");sleep(2);NSLog(@"1结束");[lock unlock];});dispatch_async(dispatch_get_global_queue(0, 0), ^{[lock lock];NSLog(@"2开始");sleep(2);NSLog(@"2结束");[lock unlock];}); }运行结果:

相关文章:
【iOS】GCD
参考文章:GCD函数和队列原理探索 之前写项目的时候,进行耗时的网络请求使用GCD处理过异步请求,但对一些概念都很模糊,这次就来系统学习一下GCD相关 相关概念 什么是GCD? Grand Center Dispatch简称GCD,是…...
C语言 | Leetcode C语言题解之第282题给表达式添加运算符
题目: 题解: #define MAX_COUNT 10000 // 解的个数足够大 #define NUM_COUNT 100 // 操作数的个数足够大 long long num[NUM_COUNT] {0};long long calc(char *a) { // 计算表达式a的值// 将数字和符号,入栈memset(num, 0, sizeof(num));in…...
如何使用 API list 极狐GitLab 容器镜像仓库中的 tag?
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab :https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署…...
粒子群算法PSO优化BP神经网络(PSO-BP)回归预测——Python和MATLAB实现
下面是一个使用Python实现的粒子群算法(PSO)优化反向传播神经网络(BP)的示例代码。 以下是具体的代码实现: python import numpy as np from sklearn.datasets import make_regression from sklearn.model_selection…...
React-router路由配置及跳转
1、V6对比V5的修改内容 1、API: useNavigate 代替了useHistory 。 2、废弃了Route组件的exact属性。 3、组件 <Routes>代替了<Switch> 4、组件NavLink中移除了 activeStyle activeClassName 属性。 2、安装依赖react-router-dom npm install react-router-dom…...
vue3【实战】可编辑的脱敏信息
<script lang"ts" setup> import { ref, onMounted } from "vue"; let real_name ref("朝阳");let name ref("");onMounted(() > {name.value des_name(real_name.value); });function focusing() {name.value real_name…...
S71200 - 笔记
1 S71200 0 ProfiNet - 2 PLC编程 01.如何零基础快速上手S7-1200_哔哩哔哩_bilibili 西门子S7-1200PLC编程设计学习视频,从入门开始讲解_哔哩哔哩_bilibili...
linux系统查历史cpu使用数据(使用sar 查询cpu和网络占用最近1个月历史数据)。
一 sar 指令介绍 在 Linux 系统中,sar 是 System Activity Reporter 的缩写,是一个用于收集、报告和保存系统活动信息的工具。它是 sysstat 软件包的一部分,提供了丰富的系统性能数据,包括 CPU、内存、网络、磁盘等使用情况&am…...
Edge浏览器加载ActiveX控件
背景介绍 新版Edge浏览器也是采用Chromium内核,虽然没有谷歌浏览器市场占有率高,但是依托微软操作系统的优势,Edge浏览器还是发展很强劲,占据着市场第二的位置。随着微软停止服务IE浏览器,曾经风光无限的IE浏览器页退出…...
BUG与测试用例设计
一.软件测试的生命周期 需求分析→测试计划→测试设计,测试开发→测试执行→测试评估→上线→运行维护 二.BUG 1.bug的概念 (1)当且仅当规格说明(需求文档)是存在的并且正确,程序与规格说明之间的不匹配才是错误. (2)当需求规格说明书没有提到的功能,判断标准以最终用户为准…...
怎么在使用select2时,覆盖layui的下拉框样式
目录 1.覆盖下拉框样式代码 2.自定义样式 3.样式使用 1.覆盖下拉框样式代码 .layui-form-select .layui-select-title {border: none !important; /* 去除边框 */background-color: transparent !important; /* 去除背景色 */display: none;/* 其他你想要覆盖的样式 */} 2.自…...
MacOSM1 配置Miniconda环境,并设置自启动
文章目录 设置环境变量设置自启动参考 设置环境变量 cd vim .zshrc输入一下内容 # 配置Conda CONDA_HOME/Users/hanliqiang/miniconda3 PATH$CONDA_HOME/bin:$PATH生效配置 source .zshrc设置自启动 conda init zsh.zshrc 文件中将会出现以下内容 # >>> conda i…...
poi库简单使用(java如何实现动态替换模板Word内容)
目录 Blue留言: Blue的推荐: 什么是poi库? 实现动态替换 第一步:依赖 第二步:实现word模板中替换文字 模板word: 通过以下代码:(自己建一个类,随意取名…...
机器人开源调度系统OpenTcs6二开-车辆表定义
前面已经知道opentcs 需要车辆的模型结构数据,将里面的数据结构化,已表的形式生成,再找一个开源的基础框架项目对车辆进行增删改的管理 表结构: CREATE TABLE Vehicle (id INT AUTO_INCREMENT PRIMARY KEY COMMENT 唯一标识符,n…...
麦歌恩MT6521-第三代汽车磁性角度传感器芯片
磁性编码芯片 -在线编程角度位置IC 描述: MT6521是麦歌恩微电子推出的新一代基于水平霍尔及聚磁片(IMC)技术原理的磁性角度和位置检测传感器芯片。该芯片内部包含了两对互成90放置的水平霍尔阵列及聚磁片,能够根据不同的型号配置来实现对XY࿰…...
【数据结构】堆,优先级队列
目录 堆堆的性质大根堆的模拟实现接口实现构造方法建堆入堆判满删除判空获取堆顶元素 Java中的PriorityQueue实现的接口构造方法常用方法PriorityQueue注意事项 练习 堆 如果有一个集合K {k0,k1, k2,…,kn-1},把它的…...
2024 暑假友谊赛 2
Tree Queries - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路:LCA好题,也有看见用时间戳写的,不是很明白. 用LCA非常好写。 要想到,给出k个节点,要确定一条路径,使得给出的k个点要么在路径上,要么和路径中某点的…...
c++ 线程
在 C 中,std::thread 构造函数可以用于将参数传递给线程。这里是一个基本的示例,展示了如何使用 std::thread 来传递参数: #include <iostream> #include <thread>// 定义一个被线程调用的函数 void threadFunc(int arg1, doubl…...
【SpringBoot】URL映射之consumes和produces匹配、params和header匹配
4.2.3 consumes和produces匹配 //处理request Content-Type为"application/json"类型的请求 RequestMapping(value"/Content",methodRequestMethod.POST,consumes"application/json") public String Consumes(RequestBody Map param){ return…...
vscode配置django环境并创建django项目(全图文操作)
文章目录 创建项目工作路径下载python插件:创建虚拟环境1. 命令方式创建2. 图文方式创建 在虚拟环境中安装Django创建Django项目安装Django插件选择虚拟环境 创建项目工作路径 输入 code . 下载python插件: 创建虚拟环境 1. 命令方式创建 切换在工作目…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
