【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 = queuedef run(self):while True:#消费者端,从队列中获取numnum = 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 processedqueue.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 = queuedef run(self):done = Falsewhile not done:#消费者端,从队列中获取numnum = self.queue.get()if num is None:done = Trueelse: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的性能优化及异常处理】
下篇笔记主要内容:【待更新】

说明

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

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

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

相关文章

下一代Docker会让部署更丝滑吗

下一代Docker会让部署更丝滑吗 如何通俗易懂的理解DockerDocker有什么缺点Docker与AI结合,会让部署更加丝滑吗 随着互联网技术的不断发展,单机系统已经无法满足日益正常的用户量以及正常处理用户请求,这个时候就需要进行多机部署,…

k8s-helloword部署一个应用

k8s-helloword部署一个应用 快速部署一个pod命令 部署一个名为 test-nginx Pod 方式一:使用 kubectl run kubectl run test-nginx --imagenginx然后使用 kubectl get pod 查看,kubectl get pod 是查看默认名称空间下的Pod 如果想要跟详细的查看这个…

四元数学习总结(1)

导语:相比矩阵,用四元数处理3D旋转的优势是毋庸置疑的,但由于概念复杂,难于理解,一直令我摸不着头脑。最近学习更是发现在机器人、无人机、SLAM等先进领域,四元数被当成实数、整数这样的基础,所…

SQLiteOpenHelper数据库帮助器

SQLiteOpenHelper数据库帮助器是Android提供的数据库辅助工具。 1、继承SQLiteOpenHelper类,需要重写onCreate和onUpgrade两个方法 案例:实现增删改查 package com.example.databases_text;import android.app.PictureInPictureParams; import androi…

FPGA 纯逻辑arinc818 ip core

1、 符合FC-FS、FC-AV、FC-ADVB协议规范; 2、符合ARINC818协议规范; 3、支持光纤通信Class1、Class3服务; 5、可动态配置光纤端口速率,支持1.0625Gbps、2.125Gbps、3.1875Gbps、4.25Gbps可配置; 6、DDR控制接口简洁…

企业级架构及本体论最新进展

本文主要探讨了企业级架构和本体论的新兴趋势,特别是DoDAF、IDEAS、UAF和NAF的发展历程、理论基础、模型构建以及与ArchiMate和语义网技术的关联。原文: The emerging landscape of Enterprise Architecture and Ontology 导言 动机和采用的方法 关注我的人都知道我…

如何修复 System has not been booted with systemd 报错信息?

如何修复 System has not been booted with systemd 报错信息? 一、问题描述: 我们在学习 linux 系统时,使用 systemd 命令(比如 sudo systemctl status ssh),可能会遇到一个报错信息: System…

【图论】最短路(一)

发现之前做的题很乱,用小笔记把看过的博客和题目分类记录一下, 代码参考了很多佬,是标注出来的链接,若不同意我就删掉(鞠躬) 找了几张好点的,图来源图中的id和acwing 1.dijkstra 依次找到距…

KubeKey 安装 K8s

官网教程 在 Linux 上以 All-in-One 模式安装 KubeSphere 步骤 1:准备 Linux 机器 若要以 All-in-One 模式进行安装,您仅需参考以下对机器硬件和操作系统的要求准备一台主机。 硬件推荐配置 操作系统最低配置Ubuntu 16.04, 18.04, 20.04, 22.042 核 …

windows服务器安装TortoiseSVN教程

TortoiseSVN也称小乌龟~ 下载链接: https://www.liqucn.com/rj/91608.shtml 下载完成后,先安装TortoiseSVN,安装完成后,根据需要安装中文包 安装比较简单直接下一步即可,注意安装路径根据需要调整到非c盘。 安装中…

超详细的前后端实战项目(Spring系列加上vue3)前端篇+后端篇(三)(一步步实现+源码)

好了,兄弟们,继昨天的项目之后,开始继续敲前端代码,完成前端部分(今天应该能把前端大概完成开启后端部分了) 昨天补充了一下登录界面加上了文章管理界面和用户个人中心界面 完善用户个人中心界面 修改一…

新手做抖音小店应该注意哪些问题?怎么正确的做抖音小店?

大家好,我是电商花花。 我们想做好一家抖音小店,想长期持久的做好一家抖店,一定要注意下面这些问题,只有避开这些做店的坑,我们才能稳稳的出单,稳稳的赚钱。 做抖音小店不能无脑铺货,要做精细…

【正点原子Linux连载】 第四十七章 音频驱动实验摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1)实验平台:正点原子ATK-DLRK3568开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id731866264428 3)全套实验源码手册视频下载地址: http://www.openedv.com/docs/boards/xiaoxitongban 第四十…

qt5core.dll怎么下载,qt5core.dll下载安装详细教程

不知道大家有没有遇到过qt5core.dll丢失这个问题?目前这个问题还是比较常见的,一般使用电脑比较多的的人,有很大几率遇到这种qt5core.dll丢失的问题。今天主要针对这个问题,来给大家讲解一下一键修复qt5core.dll的方法。 Qt5Core.…

清理安卓手机广告

保存脚本另存为 Fuck_AD.sh,在手机执行后体验效果。 echo ""echo " " echo " - 开始执行清理广告库文件" sleep 3files(/data/app/*/*/lib/arm64/libpangleflipped.so/data/app/*/*/lib/arm64/libzeus_direct_dex.so/data/app/*/*/l…

Django 安装步骤

步骤如下 打开cmd输入命令行 pip install django上图代表已经安装好了。但是里面的warning必须得将路径弄好,不然是运行不了 创建django项目 去到VS Code里,进入Terminal 页面,运行下面的命令 django-admin startproject [自己项目名称]就…

Python 小游戏——贪吃蛇

Python 小游戏——贪吃蛇 文章目录 Python 小游戏——贪吃蛇项目介绍环境配置代码设计思路1. 初始化和变量定义2. 创建游戏窗口和FPS控制器3. 初始化贪吃蛇和食物的位置4. 控制贪吃蛇的方向和分数5. 主游戏循环 难点分析源代码呈现代码结果 项目介绍 贪吃蛇游戏是一款通过上下…

智研未来,直击 AI DevOps,阿里云用户交流日杭州站来啦!

在这个技术日新月异的时代,云上智能化 DevOps 正以前所未有的速度推动企业创新边界,重塑软件开发的效率与品质。 为深入探索这一变革之路,诚邀您参与我们的专属闭门技术沙龙,携手开启一场关于云上智能化 DevOps 的挑战、实践与未…

深度学习设计模式之桥接模式

文章目录 前言一、介绍二、详细分析1.核心组成2.实现步骤3.代码示例4.优缺点优点缺点 5.使用场景 总结 前言 桥接模式是将抽象部分与实现部分分离,使它们都可以独立的变化。 一、介绍 桥接模式是结构型设计模式,主要是将抽象部分与实现部分分离&#x…