【QT八股文】系列之篇章3 | QT的多线程以及QThread与QObject

【QT八股文】系列之篇章3 | QT的多线程

  • 前言
  • 4. 多线程
    • 为什么需要使用线程池
    • 线程池的基础知识
    • python中创建线程池的方法
      • 使用threading库+队列Queue来实现线程池
      • 使用threadpool模块,这是个python的第三方模块,支持python2和python3
    • QThread的定义
    • QT多线程知识点
    • 怎么做多线程(原理篇)
      • 方案1:信号与线程
      • 方案2:线程
      • 方案3:线程与队列
    • QT多线程的使用方法(具体方法篇)
    • QT 多线程/QT线程同步的方法
  • 5. QThread与QObject
    • QThread的定义
    • 对QObject的理解
    • Q_OBJECT的作用是什么,内部实现了些什么
    • QObject是否是线程安全的/线程依附性是否可以改变/如何安全调用
  • 下一章笔记
  • 说明

前言

  • 第一篇章主要是基础定义及QT中重要的事件机制
    笔记链接:【QT八股文】系列之篇章1 | QT的基础知识及事件/机制
  • 第二篇章主要是QT的信号与槽以及通讯流程
    笔记链接:【QT八股文】系列之篇章2 | QT的信号与槽及通讯流程

这里我们主要件点更实际的,也就是多线程以及QThread与QObject
因为介绍到信号与槽,所以笔者我会讲通讯流程提前在前面来介绍

原创文章,未经同意请勿转载

4. 多线程

为什么需要使用线程池

  • 减少系统开销:频繁创建/销毁线程的开销大,影响处理效率。而在线程池缓存线程可用已有的闲置线程来执行新任务,避免了创建/销毁带来的系统开销
  • 避免阻塞问题:线程并发数量过多,抢占系统资源从而导致阻塞。线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况。
  • 管理和控制线程:运用线程池可以进行多线程的调度,有利于延迟执行,优先级执行和定时循环执行策略

线程池的基础知识

这里必须要知道线程池实现主要依靠两个部分,一个是任务队列,另外一个是线程的管理控制中心
很明显。任务队列的数据结构就是队列,先进先出,用Queue模块实现,那先了解一下Queue:

  • Queue的常用方法
    • Queue.qsize():返回queue的大小。
    • Queue.empty():判断队列是否为空,通常不太靠谱。
    • Queue.full():判断是否满了。
    • Queue.put(item, block=True, timeout=None): 往队列里放数据。
    • Queue.put_nowait(item):往队列里存放元素,不等待
    • Queue.get(item, block=True, timeout=None): 从队列里取数据。
    • Queue.get_nowait(item):从队列里取元素,不等待
    • Queue.task_done():表示队列中某个元素是否的使用情况,使用结束会发送信息。
    • Queue.join():一直阻塞直到队列中的所有元素都执行完毕。

python中创建线程池的方法

这里必须要知道线程池实现主要依靠两个部分,一个是任务队列,另外一个是线程的管理控制中心

  1. 使用threading库+队列Queue来实现线程池
  2. 使用threadpool模块,这是个python的第三方模块,支持python2和python3
  3. 使用concurrent.futures模块,这个模块是python3中自带的模块,python2.7以上版本也可以安装使用

使用threading库+队列Queue来实现线程池

1、创建一个 Queue.Queue() 的实例,然后使用数据对它进行填充。
2、将经过填充数据的实例传递给线程类,后者是通过继承threading.Thread 的方式创建的。
3、生成守护线程池。
4、每次从队列中取出一个项目,并使用该线程中的数据和 run 方法以执行相应的工作。
5、在完成这项工作之后,使用 queue.task_done() 函数向任务已经完成的队列发送一个信号。
6、对队列执行 join 操作,实际上意味着等到队列为空,再退出主程序。

在使用这个模式时需要注意一点:通过将守护线程设置为 true,程序运行完自动退出。好处是在退出之前,可以对队列执行 join 操作、或者等到队列为空。

  • 代码

    import Queue
    import threading
    import time
    queue = Queue.Queue()
    class ThreadNum(threading.Thread):
     def __init__(self, queue):
     threading.Thread.__init__(self)
     self.queue = queue
     def run(self):
     while True:
     #消费者端,从队列中获取num
     num = self.queue.get()
     print("Retrieved", num)
     time.sleep(1) 
     #在完成这项工作之后,使用 queue.task_done() 函数向任务已
    经完成的队列发送一个信号
     self.queue.task_done()
     
     print("Consumer Finished")
    def main():
     #产生一个 threads pool, 并把消息传递给thread函数进行处理,这
    里开启10个并发
     for i in range(5):
     t = ThreadNum(queue)
     t.setDaemon(True)
     t.start()
     
     #往队列中填数据
     for num in range(10):
     queue.put(num)
     #wait on the queue until everything has been processed
     
     queue.join()
     
    if __name__ == '__main__':
     main()
     time.sleep(500)
    输出为:
    ('Retrieved', 0)
    ('Retrieved', 1)
    ('Retrieved', 2)
    ('Retrieved', 3)
    ('Retrieved', 4)
    ('Retrieved', 5)
    ('Retrieved', 6)
    ('Retrieved', 7)
    ('Retrieved', 8)
    ('Retrieved', 9)
    

注意运行main函数后继续执行time.sleep(500),可以观察到主线程未结束的情况下ThreadNum(queue)生成的线程还在运行。如果需要停止线程的话可以对以上代码加以修改

  • 代码

    import Queue
    import threading
    import time
    queue = Queue.Queue()
    class ThreadNum(threading.Thread):
     """没打印一个数字等待1秒,并发打印10个数字需要多少秒?"""
     def __init__(self, queue):
     threading.Thread.__init__(self)
     self.queue = queue
     def run(self):
     done = False
     while not done:
     #消费者端,从队列中获取num
     num = self.queue.get()
     if num is None:
     done = True
     else:
     print("Retrieved", num)
     time.sleep(1) 
     #在完成这项工作之后,使用 queue.task_done() 函数向任务已
    经完成的队列发送一个信号
     self.queue.task_done()
     
     print("Consumer Finished")
    def main():
     #产生一个 threads pool, 并把消息传递给thread函数进行处理,这
    里开启10个并发
     for i in range(5):
     t = ThreadNum(queue)
     t.setDaemon(True)
     t.start()
     
     #往队列中填错数据
     for num in range(10):
     queue.put(num)
     
     queue.join()
     time.sleep(100)
     for i in range(10):
     queue.put(None)
     print('None')
     time.sleep(200)
     
    if __name__ == '__main__':
     start = time.time()
     main()
     print"Elapsed Time: %s" % (time.time() - start)
    

    main函数执行完后队列向线程发送None消息,触发线程的停止标识,这样就可以动态管理线程池了。

使用threadpool模块,这是个python的第三方模块,支持python2和python3

在这里插入图片描述

QThread的定义

QThread 是 QT 中用于创建线程的类,它提供了一组方法用于启动、停止、监测线程的运行状态,以及获取线程的相关信息。

QThread 类包含了多个方法,用于启动、停止、监测线程的运行状态,以及获取线程的相关信息。其中:

  • run()方法启动线程的执行
  • stop()方法停止线程的执行
  • join()方法等待线程的执行完毕
  • detach() 方法将线程从事件循环中移除。

此外,setName()setId()setPriority()getPriority()setSignalsBlocked()isSignalsBlocked()等方法用于修改线程的属性。

QT多线程知识点

多线程运行机制:当启动多线程后,注册信号,槽函数为主线程中的函数,当任务完成后,发射信号,在主线程中对UI进行更新。【QThred】
在这里插入图片描述

怎么做多线程(原理篇)

在这里插入图片描述

方案1:信号与线程

程序启动,创建一个线程(存活周期:直到软件关闭),当点击事件发生,发送信号,该信号连接两个槽,A负责界面变化切换,B进行后台通讯,B通讯结束,再通过信号将结果返回到界面切换,通过这种机制实现界面与通信的分离。

💡 结论:
经过代码测试,发现这个方案并非是异步的,而是同步的,原因是同时连接两个槽,这个槽机制应该是一个列表,串行执行的,必然两个槽的执行会存在先后问题,当一个阻塞,另一个也就阻塞了。

方案2:线程

程序启动,当点击事件发生,发送信号,该信号连接一个槽,槽负责界面变化切换,同时创建一个线程(存活周期:报文发送接收完成既关闭),线程进行后台通讯,通讯结束,再通过信号将结果返回到界面切换,通过这种机制实现界面与通信的分离。线程中发送的数据要通过线程创建时传入。

💡 结论:
该方法虽然实现了界面切换与通讯的异步处理,但是每点击一次按钮,都需要一次线程的创建,而且对于一直保持通讯的心跳机制,还需要单独起一个线程,可谓是花费巨大,感觉不是很好的方法

方案3:线程与队列

为了解决方案2中频繁创建线程的问题,现在做如下改进,程序启动,创建一个线程(存活周期:直到软件关闭),在线程中创建多个队列,线程监控队列,队列分别有信号队列,信息发送队列,当前界面位置队列,当界面事件发生,去修改队列,线程则监控队列,取出队列进行处理,处理之后将结果返回

💡 结论:
这样的处理机制避免了线程的频繁创建,同时能存储一些全局的重要信息,也实现了异步的效果。

QT多线程的使用方法(具体方法篇)

  1. 方法一:利用python的threading库实现(主要使用threading.Thread类)

    1. 线程启动使用start()函数
    2. 如果需要等待线程执行使用join,这样主线程会阻塞

    💡 使用join方法会让主线程阻塞在这里,等待子线程结束,在里面可以设置阻塞的时间

  2. 方法二:继承QThread,并重写run函数
    使用QThread类来创建和管理多线程。具体步骤包括:继承QThread类并重写其run()函数,将需要在子线程中执行的代码放入run()函数中【保证线程安全】;在主线程中创建QThread对象,将其指针作为参数传递给需要在子线程中执行的对象;调用QThread对象的start()函数来启动子线程。在线程任务执行过程中,可以使用 QThread 的 join() 方法等待线程执行完毕。

    ① 创建一个类从QThread类派生

    ② 在子线程类中重写 run 函数, 将处理操作写入该函数中

    ③ 在主线程中创建子线程对象, 启动子线程, 调用start()函数

    💡 需要注意的事项:如果是while循环,想要结束线程,调用QThread::quit是没有用,因为这样的线程根本就不需要事件循环,比较好的方法就是把while内的控制变量设置为false或者直接使用Qt很不推荐的方法QThread::terminate。terminate()强制退出。

  3. 方法三:使用线程池

    QtConcurrent运行一个线程池,它是一个更高级别的API,不适合运行大量的阻塞操作;如果你做了很多阻塞操作,你很快就会耗尽池并让其他请求排队,在那种情况下,QThread(较低级别的构造)可能更适合于操作(每个代表一个线程)。

  4. 方法四:利用QRunnable 类

    QRunnable 类是 PyQt5 中的可运行对象类,它提供了 run() 方法来执行线程,并可以通过 QRunnableInterface 实现线程通信。QRunnable 类创建线程的基本原理是创建一个 QRunnable 实例,并将其作为参数传递给 QApplication 的 thread() 方法创建线程。在线程任务执行过程中,可以使用 QRunnable 的 run() 方法执行线程任务。QRunnable 对象可以访问主线程的 QCoreApplication 对象。在 QRunnable 对象中,需要使用 start() 方法启动线程,并使用 join() 方法等待线程执行完毕。

  5. 方法五:继承QObject,并将对象移动至子线程(&QThread)

    ① 将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数

    ② 在主线程中创建一QThread类对象

    ③ 在主线程中创建一个业务类对象

    ④ 将业务类对象移动到子线程中

    ⑤ 在主线程中启动子线程

    ⑥ 通过信号槽的方式, 执行业务类中的业务处理函数

    💡 多线程使用注意事项:

    1. 业务对象, 构造的时候不能指定父对象

    2. 子线程中不能处理ui窗口(ui相关的类)

    3. 子线程中只能处理一些数据相关的操作, 不能涉及窗口

QT 多线程/QT线程同步的方法

  1. 使用 QMutex 对象:QMutex 是 QT 中用于线程同步的同步原语。每个线程都可以访问一个 QMutex 对象,通过 lock() 和 unlock() 方法实现线程同步。当一个线程需要访问共享资源时,它会首先尝试获取 QMutex 对象的锁,如果锁已经被其他线程获取了,那么该线程将被阻塞,直到锁被释放。
  2. 使用 QSemaphore 对象:QSemaphore 是 QT 中用于线程同步的同步原语。每个线程都可以访问一个 QSemaphore 对象,通过 semaphore.wait() 和 semaphore.signal() 方法实现线程同步。当一个线程需要访问共享资源时,它会首先尝试等待 QSemaphore 对象的许可,如果许可已经被其他线程获取了,那么该线程将被阻塞。
  3. 使用 QWaitCondition 对象:QWaitCondition 是 QT 中用于线程同步的同步原语。它结合了 QMutex 和 QSemaphore 的特点,可以更方便地实现线程同步。QWaitCondition 对象包含一个互斥锁和一个信号槽,当一个线程需要等待条件满足时,它会挂起并等待互斥锁的释放,当条件满足时,该线程会被唤醒并执行相应的操作。
  4. 使用 QEventLoop 对象:QEventLoop 是 QT 中用于处理事件循环的类,它可以实现线程同步。每个线程都可以创建一个 QEventLoop 对象,当线程需要访问共享资源时,它会进入 QEventLoop 对象的 eventLoop() 方法,等待事件处理完毕再继续执行。

在这里插入图片描述

  1. QReadWriteLock类
    》一个线程试图对一个加了读锁的互斥量进行上读锁,允许;
    》一个线程试图对一个加了读锁的互斥量进行上写锁,阻塞;
    》一个线程试图对一个加了写锁的互斥量进行上读锁,阻塞;、
    》一个线程试图对一个加了写锁的互斥量进行上写锁,阻塞。
    读写锁比较适用的情况是:需要多次对共享的数据进行读操作的阅读线程。
    QReadWriterLock 与QMutex相似,除了它对 “read”,"write"访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
  2. 信号量QSemaphore
    但是还有些互斥量(资源)的数量并不止一个,比如一个电脑安装了2个打印机,我已经申请了一个,但是我不能霸占这两个,你来访问的时候如果发现还有空闲的仍然可以申请到的。于是这个互斥量可以分为两部分,已使用和未使用。
  3. QReadLocker便利类和QWriteLocker便利类对QReadWriteLock进行加解锁

5. QThread与QObject

QThread的定义

QThread 是 QT 中用于创建线程的类,它提供了一组方法用于启动、停止、监测线程的运行状态,以及获取线程的相关信息。

QThread 类包含了多个方法,用于启动、停止、监测线程的运行状态,以及获取线程的相关信息。其中:

  • run()方法启动线程的执行
  • stop()方法停止线程的执行
  • join()方法等待线程的执行完毕
  • detach() 方法将线程从事件循环中移除。

此外,setName()setId()setPriority()getPriority()setSignalsBlocked()isSignalsBlocked()等方法用于修改线程的属性。

对QObject的理解

  • Q_OBJECT 是 Qt 框架中的一个宏定义,用于在类的声明中标记该类需要使用 Qt 的元对象系统(Meta-Object System)。使用 Q_OBJECT 宏定义后,编译器会在编译期自动生成元对象代码,包括信号(signal)和槽(slot)的注册、元对象信息的注册等等。
  • QObject 类是Qt 所有类的基类。
  • QObject是Qt对象模型的核心。这个模型的中心要素就是一种强大的叫做信号与槽无缝对象沟通机制。你可以用 connect() 函数来把一个信号连接到槽,也可以用disconnect() 函数来破坏这个连接。为了避免永无止境的通知循环,你可以用blockSignal() 函数来暂时阻塞信号。保护函数 connectNotify() 和 disconnectNotify() 可以用来跟踪连接。

对象树都是通过QObject 组织起来的,当以一个对象作为父类创建一个新的对象时,这个新对象会被自动加入到父类的 children() 队列中。这个父类有子类的所有权。能够在父类的析构函数中自动删除子类。可以通过findChild()和findChildren() 函数来寻找子类。

每个对象都一个对象名称objectName() ,而且它的类名也可以通过metaObject()函数。你可以通过inherits() 函数来决定一个类是否继承其他的类。当一个对象被删除时,它会发射destory() 信号,你可以抓住这个信号避免某些事情。

对象可以通过event() 函数来接收事情以及过滤来自其他对象的事件。就好比installEventFiter() 函数和eventFilter() 函数。childEvent() 函数能够重载实现子对象的事件。

QObject还提供了基本的时间支持,QTimer类 提高了更高层次的时间支持。

任何对象要实现信号与槽机制,Q_OBJECT 宏都是强制的。你也需要在源原件上运行元对象编译器。不管是否真正用到信号与槽机制,最好在所有QObject子类使用Q_OBJECT宏,以避免出现一些不必要的错误。

所有的Qt widgets 都是基础QObject。如果一个对象是widget,那么isWidgetType()函数就能判断出。

Q_OBJECT的作用是什么,内部实现了些什么

Q_OBJECT 是 Qt 框架中的一个宏定义,用于在类的声明中标记该类需要使用 Qt 的元对象系统(Meta-Object System)。使用 Q_OBJECT 宏定义后,编译器会在编译期自动生成元对象代码,包括信号(signal)和槽(slot)的注册、元对象信息的注册等等。

具体来说,使用 Q_OBJECT 宏定义后,编译器会为该类生成一个 QMetaObject 对象,该对象包含了该类的元对象信息,包括类名、信号和槽的名称、参数类型等等。这些信息可以通过 QObject::metaObject() 函数获取到。

此外,使用 Q_OBJECT 宏定义后,还可以在该类中使用信号和槽,使用 emit 关键字来发射信号,使用 connect 函数将信号和槽连接起来。这些功能都是通过 Qt 的元对象系统实现的。

需要注意的是,使用 Q_OBJECT 宏定义的类必须直接或间接继承自 QObject 类。

  • 实现原理
    Q_OBJECT 宏定义会为该类自动添加一些成员变量和成员函数,用于支持 Qt 的元对象系统。
    1、 QObject 类的虚函数 metaObject(),它返回一个描述该对象的元对象。
    2、QMetaObject 类型的静态变量,用于存储该对象的元对象。

QObject是否是线程安全的/线程依附性是否可以改变/如何安全调用

  • QObject及其所有子类都不是线程安全的(但都是可重入的)。因此,你不能有两个线程同时访问一个QObject对象,除非这个对象的内部数据都已经很好地序列化(例如为每个数据访问加锁)。
  • 可以改变QObject的线程依附性。 调用QObject::moveToThread()函数。该函数会改变一个对象及其所有子对象的线程依附性。
  • 如何安全的在另外一个线程中调用QObject对象的接口
    • 多线程机制设计
    • 将事件提交到接收对象所在线程的事件循环;当事件发出时,响应函数就会被调用。

下一章笔记

下篇笔记链接:【QT的性能优化及异常处理】
下篇笔记主要内容:【待更新】

说明

码字不易,可能当中存在某些字体错误(笔者我没有发现),如果有错误,欢迎大家指正。🤗
另外因为笔记是之前做的,这里我只把我之前做的搬移和重新排版过来,如果有知识上的错误也欢迎大家指正。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JeeSite V5.7.1:前后端技术革新与性能优化

一、引言 随着技术的快速发展,企业对快速开发平台的需求日益增加。JeeSite作为一款基于Java的快速开发平台,凭借其强大的功能和灵活的配置,已经获得了广泛的应用。近期,JeeSite发布了V5.7.1版本,该版本在前后端技术上…

世界最高的自动化立体库之一:贯通6层楼

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 自动化立体库的建设步骤主要包括以下几个方面: 收集需求:首先,与使用者、管理者或业主进行沟通,了…

【启明智显技术分享】SOM2D02-2GW核心板适配ALSA(适用Sigmastar ssd201/202D)

提示:作为Espressif(乐鑫科技)大中华区合作伙伴及sigmastar(厦门星宸)VAD合作伙伴,我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程供开发小伙伴参考。同时也用心整理了乐鑫及星宸…

大语言模型实战——搭建纯本地迷你版RAG

1. 概念 RAG(Retrieval Augmented Generation)检索增强生成,它结合了搜索技术和大语言模型的提示词功能,以搜索算法找到的信息作为背景上下文,来辅助大语言模型(Large Language Model, LLM)生成…

【两数之和】python

目录 暴力法 set()集合法,看过即存 字典法dict(),看过即存 暴力法 笑死我了,这题两层循环居然没超时 class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:nlen(nums)for i in range…

解决Element组件el-switch在Vue中值的绑定与回显问题

概要 Switch 开关表示两种相互对立的状态间的切换,多用于触发「开/关」。可当一个布尔值进行使用。 问题描述与解决 引入Element组件的switch到Vue中,可以读取switch的值,但如果放在页面中,不能回显上去。 如上图,无论值是"否"还是“是”。都不能正确渲染到页…

Spring服务启动后就执行某个方法

下边按照执行顺序前后,测试代码结果截图放到最后: 1、注解PostConstruct 时间:当前bean被创建并且所有的依赖注入完成之后执行; 使用:当前bean 所在类内的某个方法上 添加该注解;该方法没有参数&#xf…

宝塔PHP环境安装配置Xdebug

宝塔PHP环境安装配置Xdebug 安装XdebugVSCode安装插件编辑配置文件编辑配置运行调试断点快捷键其他 安装Xdebug 在宝塔中,找到PHP,打开管理页面,选择xdebug扩展,点击操作栏中的安装按钮(这里已经安装过了,…

dolphinscheduler standalone安装

官方文档:https://dolphinscheduler.apache.org/en-us/docs/3.1.3/guide/installation/standalone 1.安装(以放在/home为例) 下载见:https://download.csdn.net/download/taotao_guiwang/89311365 tar -xvzf apache-dolphinsche…

【Linux系统编程】进程概念、进程排队、进程标识符、进程状态

目录 什么是进程? 浅谈进程排队 简述进程属性 进程属性之进程标识符 进程操作之进程创建 初识fork fork返回值 原理角度理解fork fork的应用 进程属性之进程状态 再谈进程排队 进程状态 运行状态 阻塞状态 挂起状态 Linux下的进程状态 “R”(运行状…

02.爬虫---HTTP基本原理

02.HTTP基本原理 1.URI 和 URL 的区别2.HTTP 和 HTTPS 的区别3.请求过程 1.URI 和 URL 的区别 URL(Uniform Resource Locator)即-统一资源定位符 URL是用来定位和访问互联网上资源的独特标识,它包括了资源的位置(如IP地址或域名&a…

让写书人勇敢穿越纸海的迷雾

坚守纸海:让写书人勇敢穿越纸海的迷雾 你作为一位写书人,在创作过程中你需要坚守初心是非常重要的。在创作的过程中,你会遇到各种挑战和困难,你要勇敢面对迷雾中的挑战,并通过不懈的努力和决心,成功地穿越…

Redis常见数据类型(6)-set, zset

目录 Set 命令小结 内部编码 使用场景 用户画像 其它 Zset有序集合 普通指令 zadd zcard zcount zrange zrevrange ​编辑 zrangebyscore zpopmax/zpopmin bzpopmax/bzpopmin zrank/zrevrank zscore zrem zremrangebyrank zremrangebyscore Set 命令小结 …

AI绘画基础,建议用天工 AI 搞副业,30 分钟搞定儿童早教内容

现在 AI 非常火,竞争也非常激烈,尤其是国内的 AI 大模型堪称百团大战,非常内卷。 我研究 AI 大模型也有快一年的时间了,也体验了国内的很多大模型,然后我感觉如果单从用户体验的角度来讲,综合产品力最强的…

FPGA状态机设计详解

一.什么是状态机? 想象一下你正在玩一个电子游戏,角色有多种状态,比如“行走”、“跳跃”、“攻击”等。每当你按下不同的按键或者满足某些条件时,角色的状态就会改变,并执行与该状态对应的动作。这就是状态机的一个简…

修改vuetify3的开关组件v-switch在inset模式下的大小

<v-switchv-model"model":label"Switch: ${model.toString()}"hide-detailsinset></v-switch><style lang"scss" scoped> .custom-switch {:deep(.v-switch__thumb) {height: 18px !important; /* 设置开关按钮的高度 */width…

html 引用vue3 element 首次加载缩成一团 挤在一起

问题&#xff1a;原生html引用vue,element plus 分析&#xff1a; vue.js, element脚本过大&#xff0c;首次加载网络慢的话&#xff0c;会是缩在这里&#xff0c;没完全渲染 解决&#xff1a; 没加载之前&#xff0c;不显示div&#xff0c;显示一个加载提示语 改动地方app…

ISCC 2024 部分wp

文章目录 一、Misc1、Number_is_the_key2、FunZip3、擂台—— 重“隐”&#xff1b;4、RSA_KU5、时间刺客6、成语学习7、 精装四合一8、钢铁侠在解密9、有人让我给你带个话10、Magic_Keyboard11、工业互联网模拟仿真数据分析 二、Web1、还没想好名字的塔防游戏2、代码审计3、原…

curl: (60) SSL certificate problem: self-signed certificat

目录&#xff1a; 1、背景2、测试结果 1、背景 今天帮忙客户排查问题&#xff0c;报错请求超时&#xff0c;但是ping客户的ip以及测试端口都是通的&#xff0c;最终不得不从中台服务器上发起请求客户回调接口&#xff0c;报错如下&#xff1a; 怀疑是客户的证书有问题&#xf…

代码随想录算法训练营第十七天(py)| 二叉树 | 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树

513.找树左下角的值 力扣链接 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 思路 层序遍历 层序遍历之后&#xff0c;取最后一个数组的第一个元素 class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> …