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

news2024/10/5 16:07:37

【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的性能优化及异常处理】
下篇笔记主要内容:【待更新】

说明

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

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

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

相关文章

【kubernetes】探索k8s集群中金丝雀发布后续 + 声明式资源管理yaml

目录 一、K8S常见的发布方式 1.1蓝绿发布 1.2灰度发布(金丝雀发布) 1.3滚动发布 二、金丝雀发布 三、声明式管理方法 3.1YAML 语法格式 3.1.1查看 api 资源版本标签 3.1.2查看资源简写 3.2YAML文件详解 3.2.1Deployment.yaml 3.2.2Pod.yaml …

数据结构(五)

数据结构(五) 常见的排序算法内部排序交换插入选择归并基数 外部排序基于归并的 常见的排序算法 内部排序 交换 冒泡:每一次运行总会将最小的或者最大的放到前面,如果需要交换,一直在交换 快速排序*:经过…

《C++ Primer Plus》第十一章复习题和编程练习

这里写目录标题 一、复习题二、编程练习 一、复习题 1. 使用成员函数为Stonewt类重载乘法运算符,该运算符将数据成员与double类型的值相乘。注意,当用英石和磅表示时,需要进位。也就是说,将10英石8磅乘以2等于21英石2磅。 答&am…

Python语言基础学习(下)

目录 一、顺序语句 二、条件语句 (1) if (2) if - else (3) if - elif - else 缩进和代码块 空语句 pass 三、循环语句 while 循环 for 循环 continue break 四、函数 创建函数 调用函数 函数返回 函数变量 函数递归 关键字参数 五、列表和元组 创建列表 …

创新实训2024.05.25日志:Web应用技术选型

我们的web应用使用python web的fastapi框架,通过uvicorn开启web服务。 1. refs 官网文档:FastAPI (tiangolo.com) github:https://github.com/tiangolo/fastapi 2. 环境配置 python:3.11 uvicorn:0.29.0 pip install "uvicorn[stan…

【启程Golang之旅】基本变量与类型讲解

欢迎来到Golang的世界!在当今快节奏的软件开发领域,选择一种高效、简洁的编程语言至关重要。而在这方面,Golang(又称Go)无疑是一个备受瞩目的选择。在本文中,带领您探索Golang的世界,一步步地了…

嵌入式全栈开发学习笔记---C语言笔试复习大全25(实现学生管理系统)

目录 实现学生管理系统 第一步:结构体声明 第二步:重命名结构体 第三步:限定可以存储的最大学生数目 第四步:定义结构体指针数组和定义一个整型变量存放当前的人数 第五步:设计欢迎界面 第六步:设计…

【设计模式】JAVA Design Patterns——Command(事务模式)

🔍目的 将请求封装为对象,从而使你可以将具有不同请求的客户端参数化,队列或记录请求,并且支持可撤销操作。 🔍解释 真实世界例子 有一个巫师在地精上施放咒语。咒语在地精上一一执行。第一个咒语使地精缩小&#xff0…

从关键新闻和最新技术看AI行业发展(2024.5.6-5.19第二十三期) |【WeThinkIn老实人报】

写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术,同时Rocky会对这些关键信息进行解读,力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议,一起交流学习💪 欢迎大家关注Rocky的公众号&…

C++之std::is_trivially_copyable(平凡可复制类型检测)

目录 1.C基础回顾 1.1.平凡类型 1.2.平凡可复制类型 1.3.标准布局类型 2.std::is_trivially_copyable 2.1.定义 2.2.使用 2.3.总结 1.C基础回顾 在C11中,平凡类型(Trivial Type)、平凡可复制类型(TrivialCopyable&#x…

深入理解与防御跨站脚本攻击(XSS):从搭建实验环境到实战演练的全面教程

跨站脚本攻击(XSS)是一种常见的网络攻击手段,它允许攻击者在受害者的浏览器中执行恶意脚本。以下是一个XSS攻击的实操教程,包括搭建实验环境、编写测试程序代码、挖掘和攻击XSS漏洞的步骤。 搭建实验环境 1. 安装DVWA&#xff…

8.什么是HOOK

程序编译的本质是,首先计算机它只能看得懂机器码也就是只能看得懂数字,机器码学起来很费劲然后就创造了编译器这个东西,编译器它懂机器语言所以它可以跟机器沟通,而我们人可以跟编译器沟通,人跟编译器的语言就是各种各…

GBDT、XGBoost、LightGBM算法详解

文章目录 一、GBDT (Gradient Boosting Decision Tree) 梯度提升决策树1.1 回归树1.2 梯度提升树1.3 Shrinkage1.4 调参1.5 GBDT的适用范围1.6 优缺点 二、XGBoost (eXtreme Gradient Boosting)2.1 损失函数2.2 正则项2.3 打分函数计算2.4 分裂节点2.5 算法过程2.6 参数详解2.7…

不拍视频,不直播怎么在视频号卖货赚钱?开一个它就好了!

大家好,我是电商糖果 视频号这两年看着抖音卖货的热度越来越高,也想挤进电商圈。 于是它模仿抖音推出了自己的电商平台——视频号小店。 只要商家入驻视频号小店,就可以在视频号售卖商品。 具体怎么操作呢,需要拍视频&#xf…

leedcode【142】. 环形链表 II——Java解法

Problem: 142. 环形链表 II 思路解题方法复杂度Code性能 思路 1.用快慢指针找到相遇的点(快指针一次走一步,慢指针一次走两步) 2.一个指针从head开始,一个指针从相遇点开始,一次一步,相遇处即为环入口 解题…

Vmware 17安装 CentOS9

前言 1、提前下载好需要的CentOS9镜像,下载地址,这里下载的是x86_64 2、提前安装好vmware 17,下载地址 ,需要登录才能下载 安装 1、创建新的虚拟机 2、在弹出的界面中选择对应的类型,我这里选择自定义,点…

P459 包装类Wrapper

包装类的分类 1)针对八种基本数据类型相应的引用类型——包装类。 2)有了类的特点,就可以调用类中的方法。 Boolean包装类 Character包装类 其余六种Number类型的包装类 包装类和基本数据类型的相互转换 public class Integer01 {publi…

【大数据面试题】32 Flink 怎么重复读 Kafka?

一步一个脚印,一天一道面试题 首先,为什么要读过的 Kafka 数据重写读一次呢?什么场景下要怎么做呢? 答:当任务失败,从检查点Checkpoint 开始重启时,检查点的数据是之前的了,就需要…

从 0 开始实现一个网页聊天室 (小型项目)

实现功能 用户注册和登录好友列表展示会话列表展示: 显示当前正在进行哪些会话 (单聊 / 群聊) , 选中好友列表中的某个好友, 会生成对应的会话实时通信, A给B发送消息, B的聊天界面 / 会话界面能立刻显示新的消息 TODO: 添加好友功能用户头像显示传输图片 / 表情包历史消息搜…

CTF之Web_python_block_chain

这种题对于我来说只能看大佬的wp(但是这一题是wp都看不懂,只能表达一下我的理解了) (最后有简单方法,前面一种没看懂没关系) 下面这一部分是首页的有用部分 访问/source_code,得到源码: # -*-…