Python并发编程:揭开多线程与异步编程的神秘面纱

第一章:并发编程导论

1.1 并发与并行概念解析

1.1.1 并发性与并行性的区别

想象一下繁忙的厨房中多位厨师同时准备不同的菜肴——即使他们共享有限的空间和资源,也能协同工作,这就是并发性的一个生动比喻。并发性意味着多个任务在同一时间段内看似同时进行,但实际上可能交替执行。而在并行性中,多个任务真正意义上是在同一时刻由不同处理器或核心独立完成。例如,多台烤箱同时烹饪不同的菜品,每台烤箱都是一个独立的处理器,各自执行各自的烹饪任务。

1.1.2 并发编程的重要性及其挑战

在现代软件工程中,并发编程至关重要,因为它能够充分利用多核处理器的优势,有效提高系统吞吐量和响应速度。然而,它也带来了诸如竞态条件、死锁、活锁和资源争抢等问题。如同一场精心编排的芭蕾舞剧,若不妥善安排舞者们(即线程)的移动和动作(即状态变更),就可能导致舞台上的混乱甚至演出中断。

1.2 Python中的并发模型

1.2.1 全局解释器锁(GIL)的影响

全局解释器锁(Global Interpreter Lock, GIL)是Python并发编程绕不开的话题。如同独木桥上的守卫,GIL确保任何时候只有一个线程在执行Python字节码。尽管保证了内存安全,但也意味着在单个进程中,即便有多核CPU,也无法实现真正的并行计算。这对于CPU密集型任务来说,可能会导致性能瓶颈。

1.2.2 Python对多线程、多进程的支持

尽管受到GIL约束,Python依然提供了丰富的并发原语。对于多线程编程,可通过内置的threading模块创建和管理线程;而对于突破GIL限制,多进程编程则是一个可行的选择,multiprocessing模块为此提供了强大的支持。接下来我们将深入探索这两个领域,结合实例代码展示如何创建线程、解决并发问题以及在适当场合下使用多进程。例如,下面是一个简单的多线程实例:

import threadingdef worker(num):"""线程执行的任务"""print(f"Worker {num} is running.")# 创建并启动两个线程
threads = [threading.Thread(target=worker, args=(i,)) for i in range(2)]
for t in threads:t.start()
for t in threads:t.join()  # 确保所有线程执行完毕

这段代码展示了如何在Python中创建并启动两个线程来并发执行同一个函数。随着章节推进,我们将进一步讨论线程间的同步机制以及在不同场景下选择合适的并发策略。

第二章:Python多线程魔法阵

2.1 线程基础与线程生命周期

2.1.1 threading模块介绍

在Python的世界里,多线程犹如魔法师手中的魔杖,通过threading模块我们可以轻松地编织出并发执行的神奇景象。这个模块提供了创建、管理线程的基本结构,允许我们定义线程任务,进而实现任务的并发执行。线程就像一个个独立的工人,在同一个进程的工厂里各司其职,共同推进整体工作的进度。

2.1.2 创建与启动线程实例

想象一下,你正在经营一家咖啡厅,每位服务员就是一个线程,负责不同的订单任务。创建线程的过程就如同雇佣一位新服务员,为其分配特定的工作任务:

import threadingclass CoffeeOrderThread(threading.Thread):def __init__(self, order_id):super().__init__()self.order_id = order_iddef run(self):print(f"开始制作订单{self.order_id}的咖啡...")# 在此处模拟咖啡制作过程(比如耗时操作)time.sleep(2)print(f"订单{self.order_id}的咖啡已完成!")# 创建两个线程实例
order1 = CoffeeOrderThread(1)
order2 = CoffeeOrderThread(2)# 启动线程
order1.start()
order2.start()# 确保所有线程都完成工作
order1.join()
order2.join()
2.1.3 线程同步机制:锁、条件变量、信号量等

为了防止咖啡厅里的原料被同时取用造成混乱,我们需要引入同步机制。就好比给咖啡豆罐子加一把锁,只有拿到钥匙的服务员才能取用豆子:

import threadingcoffee_lock = threading.Lock()def prepare_coffee(order_id):with coffee_lock:print(f"开始为订单{order_id}磨咖啡豆...")# 磨豆子(同步操作)time.sleep(1)print(f"完成订单{order_id}的磨豆工作!")# 分别在两个线程中执行
threads = [threading.Thread(target=prepare_coffee, args=(i,)) for i in range(1, 3)]
for thread in threads:thread.start()
for thread in threads:thread.join()
2.1.4 经典多线程问题及解决方案
  • 死锁与饥饿问题
    死锁就像是咖啡厅里的服务员们都互相等待对方释放所需的资源而停滞不前。解决死锁的关键在于避免循环等待和资源抢占,可以通过设置超时、资源有序申请等方式预防。

示例:

lock1 = threading.Lock()
lock2 = threading.Lock()def deadlock_thread(id):if id == 1:lock1.acquire()print("线程1获得第一个锁")try:lock2.acquire(True, 2)  # 设置超时避免无限等待except threading.LockTimeout:print("线程1获取第二个锁超时,避免了死锁")else:print("线程1获得了两个锁,正常执行")elif id == 2:lock2.acquire()print("线程2获得第一个锁")try:lock1.acquire(True, 2)except threading.LockTimeout:print("线程2获取第二个锁超时,避免了死锁")else:print("线程2获得了两个锁,正常执行")# 创建并启动线程
thread1 = threading.Thread(target=deadlock_thread, args=(1,))
thread2 = threading.Thread(target=deadlock_thread, args=(2,))
thread1.start()
thread2.start()
  • 生产者消费者问题示例
    生产者消费者问题是多线程同步的经典案例,就像吧台服务员和烘焙师之间的协作。生产者(烘焙师)不断制作面包,消费者(服务员)则在面包做好时将其取出上桌。借助队列和条件变量可以完美解决这个问题。
import queue
import threadingbread_queue = queue.Queue(maxsize=5)
stop_event = threading.Event()def producer():while not stop_event.is_set():bread = make_bread()  # 制作面包bread_queue.put(bread)print("生产者制作了一块面包")def consumer():while not stop_event.is_set() or not bread_queue.empty():if not bread_queue.empty():bread = bread_queue.get()serve_customer(bread)  # 上桌面包print("消费者为顾客送上一块面包")def start_threads():producer_thread = threading.Thread(target=producer)consumer_thread = threading.Thread(target=consumer)producer_thread.start()consumer_thread.start()# 运行一段时间后停止time.sleep(5)stop_event.set()# 等待所有线程结束producer_thread.join()consumer_thread.join()start_threads()

2.2 Python多线程实践策略

2.2.1 I/O密集型任务中的多线程优化

在I/O密集型任务如网络请求或文件读写中,由于大量时间花费在等待外部响应而非CPU运算,多线程能够显著提高效率。即使受制于GIL,每个线程仍能在等待I/O操作时释放GIL,让其他线程有机会执行。

2.2.2 CPU密集型任务中的多线程局限性

对于纯CPU计算任务,由于GIL的存在,多线程在单个进程中并不能带来实质性的并行计算优势。这时,多进程或多进程与异步I/O的组合将是更好的选择。

2.2.3 使用concurrent.futures模块简化多线程编程

Python标准库中的concurrent.futures模块提供了高层级的异步接口,使得多线程编程变得更加简洁易用。通过ThreadPoolExecutor,我们可以方便地管理和调度一组线程,从而简化并发任务的组织和执行。

from concurrent.futures import ThreadPoolExecutorwith ThreadPoolExecutor(max_workers=5) as executor:future_to_url = {executor.submit(fetch_data, url): url for url in urls}for future in concurrent.futures.as_completed(future_to_url):url = future_to_url[future]try:data = future.result()except Exception as exc:print(f"获取 {url} 数据时发生错误: {exc}")else:process_data(data)

第三章:异步编程的奇妙世界

3.1 异步编程理念与事件驱动模型

3.1.1 单线程异步I/O概述

想象一个热闹的餐厅,一位服务员在单一的工作台上接待众多客人。虽然只有一人服务,但服务员并不会因为等待某个客人的菜单确认而闲下来,而是会在等待的同时去询问其他客人的需求,然后高效地来回穿梭于厨房与餐桌之间。这种高效的运作模式就是异步编程的一种直观体现。在计算机术语中,单线程异步I/O意味着即使在一个单独的线程中,也可以通过非阻塞式I/O操作和事件循环机制,使程序在等待I/O完成时继续处理其他任务,大大提高了系统的并发能力。

3.1.2 Python中的异步框架比较(asyncio, Twisted等)

在Python生态系统中,有两个突出的异步编程框架:asyncio 和 Twisted。asyncio 是 Python 3.4 版本起引入的标准库,以其简洁易用的 async/await 关键字和 EventLoop 构建的异步编程模型,迅速成为现代Python异步编程的主流工具。而Twisted作为历史悠久的异步框架,尤其擅长网络编程,提供了广泛的协议支持,尽管学习曲线稍陡峭,但在一些复杂的网络应用中仍然有着不可替代的地位。

3.2 Python asyncio模块详解

3.2.1 async/await关键字与协程基础

在asyncio的世界里,async关键字用于定义一个协程函数,它们像普通的函数一样可以包含任意Python语句,但是当遇到await表达式时会暂停执行,直到其后的异步操作完成。协程就好似接力赛中的运动员,当一个任务到达需要等待的环节时,它会把控制权交给下一个等待执行的协程。

import asyncioasync def fetch_data(url):response = await get_http_response(url)  # 假设get_http_response是异步HTTP请求函数return response.text()async def process_urls(urls):tasks = [fetch_data(url) for url in urls]results = await asyncio.gather(*tasks)for result in results:print(result)# 启动事件循环
loop = asyncio.get_event_loop()
try:loop.run_until_complete(process_urls(["http://example.com"]))
finally:loop.close()
3.2.2 Future、Task与EventLoop的核心组件
  • Future:代表未来某个时刻可能完成的结果,它是异步编程中的基本构造块,封装了异步操作的结果或者异常。

  • Task:是对Future的封装,增加了调度和取消功能,是EventLoop上运行的具体工作单元。

  • EventLoop:是整个异步编程的大脑,负责调度协程,监控Future的状态变化,以及处理定时器和其他异步资源。当Future完成时,EventLoop会触发适当的回调,从而推动协程的执行。

3.2.3 异步编程实战:HTTP请求、文件读写等案例
import aiohttp  # 异步HTTP客户端库async def fetch_async(url):async with aiohttp.ClientSession() as session:async with session.get(url) as response:return await response.text()async def main():html_content = await fetch_async('http://example.com')print(html_content)# 使用asyncio.run在主线程直接运行异步任务
asyncio.run(main())

3.3 异步编程的优势与应用场景

3.3.1 高并发场景下的性能提升

在高并发场景,如Web服务器、实时聊天应用或大规模数据抓取等,异步编程能够最大化利用单线程资源,减少不必要的上下文切换开销,使得单个线程可以处理更多的连接或请求,从而显著提升系统性能。

3.3.2 异步在实时数据流处理与Web服务器开发中的应用

在实时数据流处理中,例如股票交易实时报价系统、物联网传感器数据接收等,异步编程可以帮助程序及时响应各种事件,保证数据的实时性和准确性。而在Web服务器开发中,采用异步框架如Sanic、FastAPI配合

第四章:多进程编程与混合模式并发

4.1 multiprocessing模块介绍

4.1.1 进程间通信(IPC)机制

在Python中,multiprocessing模块是实现多进程编程的重要基石。相比于多线程,进程间不存在全局解释器锁(GIL)的问题,因此在CPU密集型任务上,多进程能充分利用多核CPU的优势。进程间通信(IPC, Inter-Process Communication)是多进程编程中的关键环节,通过管道(Pipe)、队列(Queue)、共享内存(Shared Memory)、信号量(Semaphore)等机制,进程间可以交换数据和同步执行状态。

例如,我们可以通过multiprocessing.Queue来在进程间传递消息:

from multiprocessing import Process, Queuedef worker(q):while True:item = q.get()  # 获取队列中的任务if item is None:break  # 若收到None,则退出循环process_item(item)q.task_done()  # 表示一项任务已完成def main():task_queue = Queue()# 创建多个工作者进程for _ in range(4):p = Process(target=worker, args=(task_queue,))p.start()# 将任务放入队列for task in tasks:task_queue.put(task)# 放入None来通知所有工作者进程结束for _ in range(4):task_queue.put(None)# 等待所有任务完成task_queue.join()# 关闭进程for p in processes:p.join()if __name__ == "__main__":main()
4.1.2 Pool类与进程池的使用

multiprocessing.Pool类为多进程编程提供了一个更便捷的方式,它可以创建一个进程池来管理一组工作进程。当你有大量任务需要执行时,进程池可以有效地分配和回收进程资源,提高执行效率。以下是一个使用进程池的例子:

from multiprocessing import Pooldef process_number(number):# 模拟耗时操作return number * numberif __name__ == "__main__":numbers = [1, 2, 3, 4, 5]with Pool(processes=3) as pool:# 使用map函数将任务分发到进程池squared_numbers = pool.map(process_number, numbers)print(squared_numbers)

4.2 多进程与多线程对比分析

4.2.1 GIL限制下为何选择多进程

全局解释器锁(GIL)是Python解释器为了数据安全而在多线程环境下引入的一个机制,但它限制了线程在多核CPU上的并行执行。因此,对于那些需要充分利用多核CPU性能的CPU密集型任务,尤其是不受GIL影响的计算密集型场景,多进程成为了理想的解决方案。多进程不仅规避了GIL的制约,还因进程间内存隔离的特点减少了潜在的数据竞争风险。

4.2.2 根据任务类型选择合适的并发模型

选择多进程还是多线程,通常取决于具体的应用场景和任务特点。对于I/O密集型任务,由于大部分时间都在等待外部I/O操作完成,多线程可以很好地利用这些空闲时间片,即便受限于GIL,也能在一定程度上提高系统响应速度。而对于CPU密集型任务,多进程能够更好地发挥多核CPU的优势,避免因GIL造成的性能瓶颈。

综合考虑资源消耗、通信开销和任务特性等因素,灵活运用多进程、多线程,甚至是结合异步编程,可以构建出既能满足性能需求又能保持代码简洁的高效并发解决方案。

第五章:异步I/O与协程的最佳实践

5.1 基于asyncio的高级特性与设计模式

5.1.1 异步上下文管理器与异步生成器

在Python的asyncio库中,异步上下文管理器(Async Context Manager)是协程编写过程中不可或缺的一部分,它帮助我们在进入和离开上下文时执行异步操作。例如,当我们需要异步打开和关闭文件时,可以利用async with语法优雅地管理资源:

import asyncio
import aiofilesasync def read_file(file_path):async with aiofiles.open(file_path, mode='r') as file:content = await file.read()return contentasync def main():content = await read_file('data.txt')print(content)# 运行主协程
asyncio.run(main())

此外,异步生成器(Asynchronous Generator)也是asyncio中的一大亮点。它允许我们在协程中使用yield关键字,这样就能逐段生成或消费数据,非常适合处理大数据流或连续的异步操作:

async def async_generator_example():for i in range(10):await asyncio.sleep(1)  # 模拟异步延迟操作yield iasync def consume_generator(gen):async for value in gen:print(f"Received value: {value}")async def main():generator = async_generator_example()await consume_generator(generator)asyncio.run(main())
5.1.2 使用协程进行复杂流程控制

在复杂的异步场景中,协程可以嵌套使用,结合asyncio.create_taskasyncio.waitasyncio.gather等函数,形成层次丰富、逻辑清晰的异步流程。例如,我们可以用协程同时发起多个HTTP请求,并等待所有请求完成:

import asyncio
import aiohttpasync def fetch_page(url):async with aiohttp.ClientSession() as session:async with session.get(url) as response:return await response.text()async def fetch_multiple_pages(urls):tasks = [asyncio.create_task(fetch_page(url)) for url in urls]responses = await asyncio.gather(*tasks)return responsesasync def main():urls = ['https://example1.com', 'https://example2.com', 'https://example3.com']page_contents = await fetch_multiple_pages(urls)for idx, content in enumerate(page_contents):print(f"Response from {urls[idx]}: {content[:100]}...")asyncio.run(main())

5.2 实战项目:构建高性能的并发服务

5.2.1 异步爬虫案例分析

设想一个异步爬虫项目,利用asyncio和aiohttp,我们可以并发地抓取多个网页的内容:

import asyncio
import aiohttp
from bs4 import BeautifulSoupasync def fetch_and_parse(url):async with aiohttp.ClientSession() as session:async with session.get(url) as response:text = await response.text()soup = BeautifulSoup(text, 'html.parser')title = soup.find('title').textreturn titleasync def crawl_websites(urls):titles = []async with aiohttp.ClientSession() as session:tasks = [asyncio.create_task(fetch_and_parse(url)) for url in urls]for response in await asyncio.gather(*tasks):titles.append(response)return titlesasync def main():urls = ['https://www.example1.com','https://www.example2.com','https://www.example3.com',]titles = await crawl_websites(urls)for title in titles:print(title)asyncio.run(main())
5.2.2 使用异步编程优化数据库操作

在数据库操作中,特别是涉及大量IO操作时,异步编程同样能大幅提升性能。例如,使用aiomysql或asyncpg库,我们可以异步地执行多个数据库查询:

import asyncio
import aiomysqlasync def fetch_records(query, params):conn = await aiomysql.connect(host='localhost', user='user', password='pass', db='test_db')async with conn.cursor() as cur:await cur.execute(query, params)records = await cur.fetchall()conn.close()return recordsasync def main():queries = [("SELECT * FROM table1 WHERE condition1", params1),("SELECT * FROM table2 WHERE condition2", params2)]tasks = [asyncio.create_task(fetch_records(query, params)) for query, params in queries]all_records = await asyncio.gather(*tasks)# 对结果进行合并或处理...asyncio.run(main())

通过上述例子,我们可以看到异步编程在处理I/O密集型任务时展现出的强大威力,它能让我们的并发服务更加高效、响应更快,充分挖掘硬件潜力,从而满足实际业务中对于高并发和实时性处理的需求。

第六章:并发编程的未来展望与实践指南

6.1 并发编程的未来趋势与技术发展

随着计算技术的进步和多核处理器的普及,我们正迎来一个并发编程的黄金时代。未来的并发编程将更加注重简化开发者的工作流程,提供更高层次的抽象和工具,同时保证程序的性能和安全性。例如,随着异步编程语言特性的不断完善,如Python中的async/await语法,开发者将能够以更直观和简洁的方式编写并发代码。此外,云计算和容器化技术的发展也将推动并发编程模式的创新,如通过微服务架构和Kubernetes等容器编排工具,实现更灵活的资源管理和服务部署。

6.2 实践指南:构建高效并发应用的策略

在构建高效的并发应用时,开发者需要综合考虑任务的性质、系统资源和预期的性能目标。对于I/O密集型任务,多线程和异步编程是提升效率的有效手段;而对于CPU密集型任务,多进程和分布式计算可能是更好的选择。此外,合理利用缓存和消息队列等中间件,可以减轻数据库压力,提高数据处理速度。开发者还应关注并发编程中的错误处理和异常管理,确保应用的稳定性和可靠性。

6.3 持续学习与实践:提升并发编程技能的途径

并发编程是一个不断发展的领域,要求开发者持续学习最新的技术和最佳实践。参与开源项目、阅读技术博客和论坛、参加技术会议和研讨会,都是获取新知识和交流经验的好途径。同时,通过实际项目中的问题解决和性能优化,可以不断提升自己的并发编程技能。

在这里插入图片描述


关注gzh不灵兔,Python学习不迷路,关注后后台私信,可进wx交流群,进群暗号【人生苦短】~~~

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

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

相关文章

秋招后端开发面试题 - Java语言基础(下)

目录 Java基础下前言面试题toString() 、String.valueof()、(String)?hashCode() 方法?hashCode 和 equals 方法判断两个对象是否相等?为什么重写 equals 时必须重写 hashCode 方法?String、StringBuffer、StringBuilder?String …

【Qt】控件的核心属性

1 🍑控件概述🍑 Widget 是 Qt 中的核⼼概念. 英⽂原义是 “⼩部件”, 我们此处也把它翻译为 “控件” .控件是构成⼀个图形化界⾯的基本要素。 Qt 作为⼀个成熟的 GUI 开发框架, 内置了⼤量的常⽤控件。这⼀点在 Qt Designer 中就可以看到端倪&#xf…

学习STM32第二十天

低功耗编程 一、修改主频 STM32F4xx系列主频为168MHz,当板载8MHz晶振时,系统时钟HCLK满足公式 H C L K H S E P L L N P L L M P L L P HCLK \frac{HSE \times PLLN}{PLLM \times PLLP} HCLKPLLMPLLPHSEPLLN​,在文件stm32f4xx.h中可修…

Flutter应用开发-几种保存简单配置的方式

文章目录 简单配置保存的几种方式使用 shared_preferences 插件优点缺点 使用 hive 插件优点 缺点使用文件存储:优点缺点 简单配置保存的几种方式 在 Flutter 开发的 Android 应用中,保存应用配置并下次启动时读取,有以下几种比较合适的方式…

LabVIEW 2024安装教程(附免费安装包资源)

鼠标右击软件压缩包,选择“解压到LabVIEW.2024”。 返回解压后的文件夹,鼠标右击“ni_labview-2024”选择“装载”。 鼠标右击“Install”选择“以管理员身份运行”。 点击“我接受上述2条许可协议”,然后点击“下一步”。 点击“下一步”。 …

asp.net结课作业中遇到的问题解决1

作业要求 实现增删改查导出基本功能。 1、如何设置使得某个背景就是一整个而不是无限填充或者是这个图片的某一部分。 这就要求在设置这一块的时候,长和宽按照背景图片的大小进行设置,比如: 如果,图片的大小不符合你的要求&am…

北大字节提出VAR新范式,GPT超越扩散、视觉生成Scaling Law

前言 来自北京大学和字节跳动的研究团队,提出了一种名为"Visual AutoRegressive (VAR) Modeling"的全新视觉生成范式。VAR 重新定义了图像的自回归学习过程,从而使得GPT风格的自回归模型首次超越扩散模型,在图像生成质量、速度和可…

node环境创建Vue项目

node环境创建Vue项目 目录 node环境创建Vue项目安装node.js安装Vue创建Vue项目 安装node.js 【1】.官网下载 【2】.选择路径 【3】配置环境变量 后面就是一路next完成安装 【4】测试 cmd输入node指令,显示版本号证明安装成功 安装Vue 【1】安装cnpm 这是由淘宝…

最新官方破解版会声会影2024永久序列号和激活码

会声会影2024是一款功能强大的视频编辑软件,它集合了视频剪辑、音频调整、特效添加等多项功能于一身,为用户提供了一个全面且易用的视频制作平台。无论是初学者还是专业视频编辑人员,都能在这款软件中找到满足自己创作需求的工具。 会声会影最…

数字签名学习

1 基本概念 数字签名是一种加密技术,用于验证信息来源的身份和数据的完整性。 就是对一个东西签上自己的名;收到的人可以验证这东西是你发的;这里是用数字的方式; 对字符串也可以签名,签名以后,还是一个…

嘉楠堪智 CanMV K230 的 CanMV-IDE 环境与 MicroPython 编程

嘉楠推出了 CanMV IDE 开发环境,可以使用 MicroPython 开发针对 CanMV K230 的各种程序,同时也提供了大量的例子程序,方便使用者学习。 嘉楠开发者社区,给出了详细的 CanMV K230 教程,可以借以快速上手。 目录 固件…

TikTok引流中海外云手机的实用功能分享

在当下,TikTok已成为全球范围内最受欢迎的社交媒体平台之一,拥有着庞大的用户群体和潜在的商业机会。为了在TikTok上实现更好的引流效果,利用海外云手机成为了一个明智的选择。接下来,我们将深入探讨海外云手机的功能以及它如何助…

卫瓴科技杨炯纬:帮助一线销售做营销 | 躬行者说

我并不假装理解营销技术。我所有关于营销技术的知识,都来自这个行业的躬行者们。他们筚路蓝缕,见证营销技术在中国的成长。Marteker邀请他们谈谈心路历程,以「身在此山中」的视角解读营销技术在中国的光荣与梦想。 「纸上得来终觉浅&#xf…

Swift - 枚举

文章目录 Swift - 枚举1. 枚举的基本用法2. 关联值(Associated Values)3. 关联值举例4. 原始值5. 隐式原始值(Implicitly Assigned Raw Values)6. 递归枚举(Recursive Enumeration)7. MemoryLayout Swift -…

ROS1快速入门学习笔记 - 07话题消息的定义与使用

目录 一、话题模型 二、自定义话题消息 1. 在功能包下创建msg目录用于存储话题文件 2. 在package.xml文件中添加功能包依赖; 3. 在CMakeLists.txt增加编译选项; 4. 完成编译 5. 配置CMakeLists.txt中的编译规则(增加发布者和订阅者&am…

Linux 第十三章

🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️‍🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C,linux 🔥座右铭:“不要等到什么都没有了…

【办公类-22-13】周计划系列(5-5)“周计划-05 周计划表格内教案部分“节日”清空改成“节日“” (2024年调整版本)Win32

背景需求: 本学期19周,用了近10周的时间,终于把周计划教案部分的内容补全了(把所有教案、反思的文字都撑满一个单元格), 一、原始教案 二、新模板内的教案 三、手动添加文字后的样式(修改教案…

STM32之HAL开发——电容按键

电容按键原理 电容器 (简称为电容) 就是可以容纳电荷的器件,两个金属块中间隔一层绝缘体就可以构成一个最简单的电容。如图 32_1 (俯视图),有两个金属片,之间有一个绝缘介质,这样就构成了一个电容。这样一个电容在电路板上非常容…

Detla lake with Java--入门

最近在研究数据湖,虽然不知道研究成果是否可以用于工作,但我相信机会总是留给有准备的人。 数据湖尤其是最近提出的湖仓一体化概念,很少有相关的资料,目前开源的项目就三个,分别是hudi, detla lake, iceberg。最终选择…

常用算法代码模板 (3) :搜索与图论

AcWing算法基础课笔记与常用算法模板 (3) ——搜索与图论 常用算法代码模板 (1) :基础算法 常用算法代码模板 (2) :数据结构 常用算法代码模板 (3) :搜索与图论 常用算法代码模板 (4) :数学知识 文章目录 0 搜索技巧1 树与图的存…