[Linux]:线程(二)

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

Windows环境不同,我们在linux环境下需要通过指令进行各操作,以下是常见操作的指令:

1. 线程互斥

1.1 基本概念

  • 临界资源: 多线程执行流共享的资源叫做临界资源。
  • 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区。
  • 互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

进程之间进行通信需要先创建第三方资源,使得不同的进程能够看到同一份资源。由于这份第三方资源可以由操作系统中的不同模块提供,所以进程间通信的方式有很多种。在进程间通信中,这个第三方资源被称为临界资源,而访问第三方资源的代码则被称为临界区。

与之不同的是,多线程的大部分资源都是共享的。因此,线程之间进行通信并不需要像进程那样费力地去创建第三方资源。

例如,我们在代码中只需要在全局区定义一个count变量,新线程可以每隔一秒对该变量进行加一操作,主线程也可以每隔一秒获取count变量的值并进行打印。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
using namespace std;
int count = 0;
void *Routine(void *args)
{while (true){count++;sleep(1);}pthread_exit((void *)0);
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, Routine, nullptr);while (true){cout << "The value of count is " << count << endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}

在当前情境下,我们相当于实现了主线程和新线程之间的通信。其中,全局变量count起着关键作用,它被称为为临界资源,原因在于它被多个执行流所共享。而主线程中的 cout 操作以及新线程中的 count++ 操作,被称作临界区。这是因为这些代码片段对临界资源进行了访问。

但是我们同样观察到打印数据并没有 1,这就是多执行流对临界资源操作常引发的数据不一致问题。

同样我们也可以下面抢票程序的实现,具体演示如果不对临界资源进行限制,可能会出现的危害。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
void *getTickets(void *args)
{string name = "thread ";name += to_string((uint64_t)args);while (true){if (tickets > 0){usleep(1000);cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;}else{break;}}cout << name << " is quit!" << endl;pthread_exit((void *)0);
}
int main()
{pthread_t tids[5];for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, getTickets, (void *)i);}for (int i = 0; i < 5; i++){pthread_join(tids[i], nullptr);}return 0;
}

剩余票数出现负数,这明显不符合我们的常识与预期,之所以出现这种情况,本质就是 tickets就是我们的临界资源,--tickets也 并不是原子的,在多执行流同时执行时就可能会发生这种问题。

为什么 --tickets并不是原子的呢?

因为从汇编角度看,我们的 --操作其实是不安全的,他们转成汇编,一般会对应三条汇编指令:从内存中读取数据到 CPU 中;CPU 内进行操作;CPU 将结果写回内存。进程在运行的时候,随时可能被切换。

1.2 互斥量

为了解决这个问题我们就引入了互斥,保证一次只有一个执行流访问临界资源,而为了实现互斥,我们就需要保证临界区的原子性,即临界区的资源要么被执行完成,要么不执行,只存在这两态。

要做到这些,本质就是需要一把锁,所以 Linux就引入一个锁,并将其称为互斥量。

画板

1.3 互斥量的接口

1.3.1 初始化互斥量

我们可以使用pthread_mutex_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  2. 参数:
  • mutex:需要初始化的互斥量。
  • attr:初始化互斥量的属性,一般设置为 nullptr 即可。
  1. 返回值:互斥量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化互斥量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

1.3.2 销毁互斥量

我们可以使用pthread_mutex_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
  2. 参数:mutex:需要销毁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

其中销毁互斥量,需要注意以下几点:

  • 使用PTHREAD_MUTEX_INITIALIZER静态初始化的互斥量不需要销毁。
  • 不能销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
1.3.3 加锁互斥量

加锁本质就是让被加锁区域的代码具有原子性,只能同时被一个线程访问。我们可以使用pthread_mutex_lock对互斥量进行加锁,使用方法如下:

  1. 函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要加锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

如果一个线程在执行过程中,遇见该接口,并且该锁已被其他线程申请,那么该线程此时就会陷入阻塞状态,等待其解锁。

1.3.4 解锁互斥量

在加完锁之后,我们不可能让所有代码只被一个执行流访问,所以我们需要合适的地方解锁,我们可以使用pthread_mutex_unlock对互斥量进行解锁,使用方法如下:

  1. 函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要解锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

知道了这些概念之后我们就可以对前面的抢票逻辑进行修改:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
pthread_mutex_t mutex;
void *getTickets(void *args)
{string name = "thread ";name += to_string((uint64_t)args);while (true){pthread_mutex_lock(&mutex);if (tickets > 0){usleep(1000);cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}cout << name << " is quit!" << endl;pthread_exit((void *)0);
}
int main()
{pthread_mutex_init(&mutex, nullptr);pthread_t tids[5];for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, getTickets, (void *)i);}for (int i = 0; i < 5; i++){pthread_join(tids[i], nullptr);}pthread_mutex_destroy(&mutex);return 0;
}

其实在大部分情况下,加锁本身都是有损于性能的事,因为它使多执行流由并行执行变为了串行执行,这几乎是不可避免的。所以我们需要在合适的位置加锁与解锁,尽可能减少锁引入锁带来的性能开销成本。

1.4 互斥量的原理

当我们使用互斥量之后,临界区的代码对于其他线程来说,只有两种状态:加锁与解锁,这就保证了临界区的原子性。而我们要知道锁本身就是能被所有执行流访问的资源,所以锁本身也是一种临界资源,当然也需要保证其原子性,所以锁本身实现就是原子的。

为了实现互斥锁操作,大多数体系结构都提供了 swapexchange 指令,该指令的作用就是把寄存器和内存单元的数据相交换,以下就是实现加锁 lock与解锁 unlock的伪代码:

我们首先可以认为 mutex 的初始值为1,al 是计算机中的一个寄存器,当线程申请锁时,需要执行以下步骤:

  1. 先将 al 寄存器中的值清0。该动作可以被多个线程同时执行,因为每个线程都有自己的一组寄存器(上下文信息),执行该动作本质上是将自己的 al 寄存器清0。
  2. 然后交换 al 寄存器和 mutex 中的值。xchgb 是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换。

画板

  1. 最后判断 al 寄存器中的值是否大于0。若大于0则申请锁成功,此时就可以进入临界区访问对应的临界资源;否则申请锁失败需要被挂起等待,直到锁被释放后再次竞争申请锁。

我们需要注意的是CPU内的寄存器不是被所有的线程共享的,每个线程都有独自的一组寄存器,所以改变当前线程 al寄存器的值并不会影响其他线程的 al寄存器, 当然内存中的数据因为属于同一个进程,所以各个线程是共享的。

画板

而当线程释放锁时,需要执行以下步骤:

  1. 将内存中的 mutex 置回1,使得下一个申请锁的线程在执行交换指令后能够得到1。
  2. 唤醒等待 mutex 的线程,让它们继续竞争申请锁。

在线程释放锁的过程中,并没有将当前线程的 al 寄存器中的值清0,这不会造成任何影响,因为每次线程在申请锁时都会先将自己 al 寄存器中的值清0,再执行交换指令。

所以我们申请锁的本质就是执行 xchgb这一条汇编指令,因为只有一条,所以只有已执行与未执行两种状态,具有原子性。

2. 线程安全

线程安全是指在多线程环境下,多个线程并发执行同一段代码时,不会出现不可预期的错误结果或数据不一致的情况。

常见线程不安全的情况有:

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

而线程安全的情况有:

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

而可重入函数与线程安全的联系有:

  • 函数是可重入的,那就是线程安全的。
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

而可重入函数与线程安全的区别有:

  • 可重入函数是线程安全函数的一种。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

3. 死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。

比如说如果某一执行流连续申请了两次锁,就会陷入死锁状态。具体情况如下:当该执行流第一次申请锁时,通常会申请成功。然而,第二次申请锁时,由于此锁已经被该执行流自身持有,再次申请会失败,进而导致该执行流被挂起。而此时,这个锁在其自己手上,可它又处于被挂起的状态,根本没有机会去释放锁。这样一来,该执行流将永远无法被唤醒,从而处于死锁状态。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void*Routine(void*args)
{pthread_mutex_lock(&mutex);pthread_mutex_lock(&mutex);return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,Routine,nullptr);pthread_join(tid,nullptr);return 0;
}

其中形成死锁的必要条件有以下四个:

  • 互斥条件: 一个资源每次只能被一个执行流使用。
  • 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系。

而为了避免死锁我们一般也可以从这几个角度思考:

  • 破坏死锁的四个必要条件。
  • 加锁顺序一致。
  • 避免锁未释放的场景。
  • 资源一次性分配。

除此之外,还有一些避免死锁的算法,常见的比如有死锁检测算法和银行家算法。

4. 线程同步

4.1 饥饿问题

线程饥饿指的是某些线程由于各种原因,一直无法获得足够的 CPU 时间来执行任务,从而处于长期等待或执行时间极少的状态。 产生线程饥饿的原因主要有以下几种:

  1. 高优先级线程抢占:如果系统中有高优先级的线程持续占用 CPU 资源,那么低优先级的线程就可能长时间得不到执行机会,从而导致饥饿。例如,在实时系统中,高优先级的实时任务可能会一直抢占低优先级的普通任务。
  2. 线程调度不公平:如果线程调度算法不合理或者存在缺陷,可能导致某些线程被不公平地对待,长期无法获得执行机会。比如某些调度算法可能偏向于某些特定类型的线程或者特定状态的线程。
  3. 资源竞争:当多个线程竞争有限的资源时,一些线程可能因为一直无法获得所需资源而被阻塞,从而无法执行。例如,多个线程竞争一个互斥锁,而某些线程总是在竞争中失败,就可能陷入饥饿状态。

线程饥饿会导致系统性能下降,部分任务无法及时完成,甚至可能使整个系统陷入停滞或出现不可预测的行为。为了解决线程饥饿问题,我们可以让线程与线程之间形成同步关系。

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。

4.2 条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述。其一般包含两个步骤:

  • 一个线程等待条件变量的条件成立而被挂起。
  • 另一个线程使条件成立后唤醒等待的线程。

4.3 条件变量的接口

4.3.1 初始化条件变量

我们可以使用pthread_cond_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  2. 参数:
  • cond:需要初始化的条件变量。
  • attr:初始化条件变量的属性,一般设置为 nullptr 即可。
  1. 返回值:条件变量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化条件变量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

4.3.2 销毁条件变量

我们可以使用pthread_cond_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
  2. 参数:mutex:需要销毁的条件变量。
  3. 返回值:成功返回 0,失败返回错误码。
  • 使用 PTHREAD_COND_INITIALIZER 静态初始化的条件变量不需要销毁。
4.3.3 等待条件变量

当某个线程满足某个条件时,我们就可以将其至于条件变量下等待。

  1. 函数原型:int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  2. 参数:
  • cond:需要等待的条件变量。
  • mutex:当前线程所处临界区对应的互斥锁。
  1. 返回值:成功返回 0,失败返回错误码。
4.3.4 唤醒等待

在满足某个条件之后,我们就可以使用以下两种即可,将等待队列中的线程唤醒。

  1. 函数原型:
  • int pthread_cond_broadcast(pthread_cond_t *cond);
  • int pthread_cond_signal(pthread_cond_t *cond);
  1. 参数:cond:需要唤醒的条件变量。
  2. 返回值:成功返回 0,失败返回错误码。

其中 pthread_cond_signal()函数用于唤醒等待队列中的第一个线程。pthread_cond_broadcast()函数用于唤醒等待队列中的全部线程。

比如我们下面创建五个线程,然后将其放入等待队列,最后由主线程进行唤醒。

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#include <string>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *Routine(void *args)
{pthread_detach(pthread_self());string name = "thread " + to_string((uint64_t)args);while (true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);cout << name << " running..." << endl;pthread_mutex_unlock(&mutex);}
}
int main()
{pthread_t tids[5];pthread_mutex_init(&mutex, nullptr);pthread_cond_init(&cond, nullptr);for (uint64_t i = 0; i < 5; i++){pthread_create(tids + i, nullptr, Routine, (void *)i);}while (true){sleep(1);pthread_cond_signal(&cond);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

在调用<font style="color:rgb(28, 31, 35);">pthread_cond_wait</font>函数时需要传入对应的互斥锁,原因如下:

当线程由于某些条件不满足而需要在特定条件变量下进行等待时,必须释放该互斥锁。这是因为如果不释放互斥锁,其他线程将无法获取该锁以进入临界区修改共享资源,从而无法改变条件使等待线程被唤醒。

当该线程被唤醒后,会接着执行临界区内的代码,这就要求该线程必须立即获得对应的互斥锁。这样设计确保了线程在被唤醒后能够安全地访问临界区,避免了多个线程同时进入临界区而导致的数据不一致和资源竞争问题。

4.4 条件变量使用规范

使用条件变量我们一般遵守以下规范,如果是等待条件变量,函数应该放在互斥量加锁与解锁之间,因为判断条件也是一种临界资源。

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
//修改条件
pthread_mutex_unlock(&mutex);

同样唤醒操作也需要类似的操作。

pthread_mutex_lock(&mutex);
//条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/435430.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理实战项目:从基础到实战

自然语言处理实战项目&#xff1a;从基础到实战 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能的重要分支&#xff0c;致力于让计算机能够理解、生成和处理人类语言。NLP 在搜索引擎、智能客服、语音助手等场景中扮演着关键角色。本文将带…

使用python进行自然语言处理的示例

程序功能 分词&#xff1a;将输入句子拆分为单词。 词性标注&#xff1a;为每个单词标注其词性。 命名实体识别&#xff1a;识别命名实体&#xff08;如人名、地名、组织等&#xff09;。 这段代码展示了如何用 nltk 进行基础的 NLP 任务&#xff0c;包括分词、词性标注和命名…

Django Web开发接口定义

Django Web 介绍 Django Web是一个Pyhton高级 Web 框架,实际上 Django 也可以做到前后端分离,即主要作为后端框架使用,不用模板渲染也是可行的。 Django Web 应用的运行流程,如下图所示: 此外,Django Web 在开发环境可以通过自带的服务器进行本地调试。但是该服务器不适…

Spring - @Import注解

文章目录 基本用法源码分析ConfigurationClassPostProcessorConfigurationClass SourceClassgetImportsprocessImports处理 ImportSelectorImportSelector 接口DeferredImportSelector 处理 ImportBeanDefinitionRegistrarImportBeanDefinitionRegistrar 接口 处理Configuratio…

从零预训练一个tiny-llama#Datawhale组队学习Task2

完整的教程请参考&#xff1a;datawhalechina/tiny-universe: 《大模型白盒子构建指南》&#xff1a;一个全手搓的Tiny-Universe (github.com) 这是Task2的学习任务 目录 Qwen-blog Tokenizer&#xff08;分词器&#xff09; Embedding&#xff08;嵌入&#xff09; RMS …

【2025】基于Django的鱼类科普网站(源码+文档+调试+答疑)

文章目录 一、基于Django的鱼类科普网站-项目介绍二、基于Django的鱼类科普网站-开发环境三、基于Django的鱼类科普网站-系统展示四、基于Django的鱼类科普网站-代码展示五、基于Django的鱼类科普网站-项目文档展示六、基于Django的鱼类科普网站-项目总结 大家可以帮忙点赞、收…

Codeforces Round 975 (Div. 2) A-C 题解

这次看到 C 题分数 1750 就开始害怕了&#xff0c;用小号打的比赛&#xff0c;一直觉得做不出来&#xff0c;最后才想到 A. Max Plus Size 题意 给你一些整数&#xff0c;选择一些涂成红色&#xff0c;两两不能相邻&#xff0c;你的得分为&#xff1a; [ 红色元素的个数 ] …

什么是 JWT?它是如何工作的?

松哥最近辅导了几个小伙伴秋招&#xff0c;有小伙伴在面小红书时遇到这个问题&#xff0c;这个问题想回答全面还是有些挑战&#xff0c;松哥结合之前的一篇旧文和大伙一起来聊聊。 一 无状态登录 1.1 什么是有状态 有状态服务&#xff0c;即服务端需要记录每次会话的客户端信…

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网

努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂ROM固件-安卓刷机固件网 统版本&#xff1a;官方软件作者&#xff1a;热心网友rom大小&#xff1a;911MB发布日期&#xff1a;2018-12-23 努比亚z17努比亚NX563j原厂固件卡刷包下载_刷机ROM固件包下载-原厂RO…

JVM相关的命令汇总

一、简介 虽然目前市场上有很多成熟的 JVM 可视化监控分析工具&#xff0c;但是所有的工具其实都依赖于 JDK 的接口和底层相关的命令&#xff0c;了解这些命令的使用对于在紧急情况下排查 JVM 相关的线上故障&#xff0c;会有更加直观的帮助。 下面一起来看看 JVM 常用的命令…

图像处理基础知识点简记

简单记录一下图像处理的基础知识点 一、取样 1、释义 图像的取样就是图像在空间上的离散化处理,即使空间上连续变化的图像离散化, 决定了图像的空间分辨率。 2、过程 简单描述一下图象取样的基本过程,首先用一个网格把待处理的图像覆盖,然后把每一小格上模拟图像的各个…

五、CAN总线

目录 一、基础知识 1、can介绍 2、CAN硬件电路 3、CAN电平标准 4、CAN收发器芯片介绍 5、CAN帧格式 ① CAN帧种类 ② CAN数据帧 ③ CAN遥控帧​编辑 ④ 位填充 ⑤ 波形实例 6、接收方数据采样 ① 接收方数据采样遇到的问题 ② 位时序 ③ 硬同步 ④ 再同步 ⑤ 波…

1.8 软件业务测试

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 概述2 方法3 测试策略4 案例分析 前言 在软件开发生命周期中&#xff0c;业务测试扮演着至关重要的角色。本文详细讲解了业务测试的定义、目的、方法以及测试策略。 本篇文章参…

信息安全数学基础(22)素数模的同余式

前言 信息安全数学基础中的素数模的同余式是数论中的一个重要概念&#xff0c;它涉及到了素数、模运算以及同余关系等多个方面。 一、基本概念 素数&#xff1a;素数是指只能被1和它本身整除的大于1的自然数。素数在密码学中有着广泛的应用&#xff0c;如RSA加密算法就依赖于大…

订餐点餐|订餐系统基于java的订餐点餐系统小程序设计与实现(源码+数据库+文档)

订餐点餐系统小程序 目录 基于java的订餐点餐系统小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布…

9.29 LeetCode 3304、3300、3301

思路&#xff1a; ⭐进行无限次操作&#xff0c;但是 k 的取值小于 500 &#xff0c;所以当 word 的长度大于 500 时就可以停止操作进行取值了 如果字符为 ‘z’ &#xff0c;单独处理使其变为 ‘a’ 得到得到操作后的新字符串&#xff0c;和原字符串拼接 class Solution { …

[CSP-J 2022] 解密

题目来源&#xff1a;洛谷题库 [CSP-J 2022] 解密 题目描述 给定一个正整数 k k k&#xff0c;有 k k k 次询问&#xff0c;每次给定三个正整数 n i , e i , d i n_i, e_i, d_i ni​,ei​,di​&#xff0c;求两个正整数 p i , q i p_i, q_i pi​,qi​&#xff0c;使 n …

verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)

在以往采用 FPGA 实现的 FIR 滤波功能&#xff0c;滤波器系数是通过 matlab 计算生成&#xff0c;然后作为固定参数导入到 verilog 程序中&#xff0c;这尽管简单&#xff0c;但灵活性不足。在某些需求下&#xff08;例如捕获任意给定台站信号&#xff09;需要随时修改滤波器的…

创建游戏暂停菜单

创建用户控件 设置样式 , 加一层 背景模糊 提升UI菜单界面质感 , 按钮用 灰色调 编写菜单逻辑 转到第三人称蓝图 推荐用 Set Input Mode Game And UI , 只用仅UI的话 增强输入响应不了 让游戏暂停的话也可以用 Set Game Paused , 打勾就是暂停 , 不打勾就是继续游戏 , 然后…

Yolov8分类检测记录

1.先到github上下载&#xff0c;ultralytics源代码 2.pycharm新建一个项目 3.准备训练数据 数据的结构如下 不需要.yaml文件&#xff0c;代码会自动识别要分的类 4.创建一个训练文件 import torch import random import cv2 import numpy as np import os from ultralytics…