python之并发编程

python之并发编程

  • 线程的创建方式
    • 线程的创建方式(方法包装)
    • 线程的创建方式(类包装)
    • join()【让主线程等待子线程结束】
    • 守护线程【主线程结束,子线程就结束】
    • 多线程操作同一个对象(未使用线程同步)
    • 多线程操作同一个对象(增加互斥锁,使用线程同步)
    • 死锁案例
    • 信号量使用
    • 事件(Event)
  • 生产者消费者模型
  • 进程
    • 方法模式创建进程
    • 类模式创建进程
    • Queue实现进程间通信
    • Pipe实现进程间通信
    • Manager管理器
  • 进程池(Pool)
    • 进程池使用案例
    • 使用with管理进程池
  • 协程是什么
    • 不使用协程执行多个任务
    • 使用yield协程,实现任务切换
    • asyncio实现协程(重点)

线程的创建方式

Python的标准库提供了两个模块: _thread 和 threading , _thread 是低级模块, threading 是高级模块,对 _thread 进行了封装。绝大多数情况下,我们只需要使用 threading 这个高级模块。
线程的创建可以通过分为两种方式:

  1. 方法包装
  2. 类包装
    线程的执行统一通过 start() 方法

线程的创建方式(方法包装)

def func1(name):print(f"线程{name},start")  #formatfor i in range(3):print(f"线程{name},{i}")sleep(3)print(f"线程{name},end")if __name__ == '__main__':print("主线程,start")#创建线程t1 = Thread(target=func1,args=("t1",))t2 = Thread(target=func1,args=("t2",))#启动线程t1.start()t2.start()print("主线程,end")
运行结果可能会出现换行问题,是因为多个线程抢夺控制台
输出的IO流。

线程的创建方式(类包装)

class MyThread(Thread):def __init__(self,name):Thread.__init__(self)self.name = namedef run(self):print(f"线程{self.name},start")  # formatfor i in range(3):print(f"线程{self.name},{i}")sleep(3)print(f"线程{self.name},end")if __name__ == '__main__':print("主线程,start")#创建线程t1 = MyThread("t1")t2 = MyThread("t2")#启动线程t1.start()t2.start()print("主线程,end")

join()【让主线程等待子线程结束】

之前的代码,主线程不会等待子线程结束。
如果需要等待子线程结束后,再结束主线程,可使用join()方法。

def func1(name):for i in range(3):print(f"thread:{name} :{i}")sleep(1)if __name__ == '__main__':print("主线程,start")#创建线程t1 = Thread(target=func1,args=("t1",))t2 = Thread(target=func1,args=("t2",))#启动线程t1.start()t2.start()#主线程会等待t1,t2结束后,再往下执行t1.join()t2.join()print("主线程,end")

守护线程【主线程结束,子线程就结束】

class MyThread(Thread):def __init__(self,name):Thread.__init__(self)self.name =namedef run(self):for i in range(3):print(f"thread:{self.name} :{i}")sleep(1)if __name__ == '__main__':print("主线程,start")#创建线程(类的方式)t1 = MyThread('t1')#t1设置为守护线程t1.daemon = True# t1.setDaemon(True)#启动线程t1.start()print("主线程,end")

多线程操作同一个对象(未使用线程同步)

class Account:def __init__(self,money,name):self.money = moneyself.name = name#模拟提款的操作
class Drawing(Thread):def __init__(self,drawingNum,account):Thread.__init__(self)self.drawingNum = drawingNumself.account = accountself.expenseTotal = 0def run(self):if self.account.money<self.drawingNum:returnsleep(1) #判断完可以取钱,则阻塞。就是为了测试发生冲突问题self.account.money -=self.drawingNumself.expenseTotal += self.drawingNumprint(f"账户:{self.account.name},余额是:{self.account.money}")print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")if __name__ == '__main__':a1 = Account(100,"gaoqi")draw1 = Drawing(80,a1)  #定义一个取钱的线程draw2 = Drawing(80,a1)  #定义一个取钱的线程draw1.start()draw2.start()

多线程操作同一个对象(增加互斥锁,使用线程同步)

class Account:def __init__(self,money,name):self.money = moneyself.name = name#模拟提款的操作
class Drawing(Thread):def __init__(self,drawingNum,account):Thread.__init__(self)self.drawingNum = drawingNumself.account = accountself.expenseTotal = 0def run(self):lock1.acquire()if self.account.money<self.drawingNum:print("账户余额不足!")returnsleep(1) #判断完可以取钱,则阻塞。就是为了测试发生冲突问题self.account.money -=self.drawingNumself.expenseTotal += self.drawingNumlock1.release()print(f"账户:{self.account.name},余额是:{self.account.money}")print(f"账户:{self.account.name},总共取了:{self.expenseTotal}")if __name__ == '__main__':a1 = Account(1000,"gaoqi")lock1 = Lock()draw1 = Drawing(80,a1)  #定义一个取钱的线程draw2 = Drawing(80,a1)  #定义一个取钱的线程draw1.start()draw2.start()

死锁案例

def fun1():lock1.acquire()print('fun1拿到菜刀')sleep(2)lock2.acquire()print('fun1拿到锅')lock2.release()print('fun1释放锅')lock1.release()print('fun1释放菜刀')def fun2():lock2.acquire()print('fun2拿到锅')lock1.acquire()print('fun2拿到菜刀')lock1.release()print('fun2释放菜刀')lock2.release()print('fun2释放锅')if __name__ == '__main__':lock1 = Lock()lock2 = Lock()t1 = Thread(target=fun1)t2 = Thread(target=fun2)t1.start()t2.start()

信号量使用

def home(name,se):se.acquire()print(f"{name}进入房间")sleep(3)print(f"****{name}走出房间")se.release()if __name__ == '__main__':se = Semaphore(5)   #信号量对象for i in range(7):t = Thread(target=home,args=(f"tom{i}",se))t.start()

事件(Event)

事件Event主要用于唤醒正在阻塞等待状态的线程;

Event 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,event 对象中的信号标志被设置假。如果有线程等待一个 event 对象,而这个 event 对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个 event 对象的信号标志设置为真,它将唤醒所有等待个 event 对象的线程。如果一个线程等待一个已经被设置为真的 event 对象,那么它将忽略这个事件,继续执行

在这里插入图片描述

def chihuoguo(name):#等待事件,进入等待阻塞状态print(f'{name}已经启动')print(f'小伙伴{name}已经进入就餐状态!')time.sleep(1)event.wait()# 收到事件后进入运行状态print(f'{name}收到通知了.' )print(f'小伙伴{name}开始吃咯!')if __name__ == '__main__':event = threading.Event()# 创建新线程thread1 = threading.Thread(target=chihuoguo, args=("tom", ))thread2 = threading.Thread(target=chihuoguo, args=("cherry", ))# 开启线程thread1.start()thread2.start()time.sleep(10)# 发送事件通知print('---->>>主线程通知小伙伴开吃咯!')event.set()

生产者消费者模型

从一个线程向另一个线程发送数据最安全的方式可能就是使用queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用 put() 和 get() 操作来向队列中添加或者删除元素。Queue 对象已经包含了必要的锁,所以你可以通过它在多个线程间多安全地共享数据。

def producer():num = 1while True:if queue.qsize()<5:print(f"生产{num}号,大馒头")queue.put(f"大馒头:{num}号")num +=1else:print("馒头框满了,等待来人消费啊!")sleep(1)def consumer():while True:print(f"获取馒头:{queue.get()}")sleep(1)if __name__ == '__main__':queue = Queue()t1 = Thread(target=producer)t2 = Thread(target=consumer)t1.start()t2.start()

进程

方法模式创建进程

def fun1(name):print(f"当前进程ID:{os.getpid()}")print(f"父进程ID:{os.getppid()}")print(f"Process:{name},start")sleep(3)print(f"Process:{name},end")#windows上多进程实现的bug。如果不加main的限制,就会无限制的创建子进程,从而报错。
if __name__ == '__main__':print("当前进程ID:",os.getpid())#创建进程p1 = Process(target=fun1,args=("p1",))p2 = Process(target=fun1, args=("p2",))#启动进程p1.start()p2.start()

类模式创建进程

class MyProcess(Process):def __init__(self,name):Process.__init__(self)self.name = namedef run(self):print(f"Process:{self.name},start")sleep(3)print(f"Process:{self.name},end")if __name__ == '__main__':#创建进程p1 = MyProcess("p1")p2 = MyProcess("p2")p1.start()p2.start()

Queue实现进程间通信

前面讲解了使用 Queue 模块中的 Queue 类实现线程间通信,但要实现进程间通信,需要使用 multiprocessing 模块中的 Queue 类。
简单的理解 Queue 实现进程间通信的方式,就是使用了操作系统给开辟的一个队列空间,各个进程可以把数据放到该队列中,当然也可以从队列中把自己需要的信息取走。

#coding=utf-8
from multiprocessing import Process, Queue
from time import sleepclass MyProcess(Process):def __init__(self,name,mq):Process.__init__(self)self.name = nameself.mq = mqdef run(self):print(f"Process:{self.name},start")print(f"get Data:{mq.get()}")sleep(2)self.mq.put(f"new_data:{self.name}")print(f"Process:{self.name},end")if __name__ == '__main__':mq = Queue()mq.put("1")mq.put("2")mq.put("3")#进程列表p_list = []for i in range(3):p = MyProcess(f"p{i}",mq)p_list.append(p)for p in p_list:p.start()for p in p_list:p.join()print(mq.get())print(mq.get())print(mq.get())

Pipe实现进程间通信

Pipe 直译过来的意思是“管”或“管道”,和实际生活中的管(管道)是非常类似的。
Pipe方法返回(conn1, conn2)代表一个管道的两个端。Pipe方法有duplex参数,如果duplex参数为True(默认值),
那么这个参数是全双工模式,也就是说conn1和conn2均可收发。若duplex为False,conn1只负责接收消息,conn2只负责
发送消息。send和recv方法分别是发送和接受消息的方法。例如,在全双工模式下,可以调用conn1.send发送消息,
conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。

#coding=utf-8
import multiprocessing
from time import sleepdef func1(conn1):sub_info = "Hello!"print(f"进程1--{multiprocessing.current_process().pid}发送数据:{sub_info}")sleep(1)conn1.send(sub_info)print(f"来自进程2:{conn1.recv()}")sleep(1)
def func2(conn2):sub_info = "你好!"print(f"进程2--{multiprocessing.current_process().pid}发送数据:{sub_info}")sleep(1)conn2.send(sub_info)print(f"来自进程1:{conn2.recv()}")sleep(1)if __name__ == '__main__':#创建管道conn1,conn2 = multiprocessing.Pipe()# 创建子进程process1 = multiprocessing.Process(target=func1,args=(conn1,))process2 = multiprocessing.Process(target=func2,args=(conn2,))# 启动子进程process1.start()process2.start()

Manager管理器

def func(name,m_list,m_dict):m_dict['name'] = '123'm_list.append('你好')if __name__ == "__main__":with Manager() as mgr:m_list = mgr.list()m_dict = mgr.dict()m_list.append('Hello!!')#两个进程不能直接互相使用对象,需要互相传递p1 = Process(target=func,args=('p1',m_list,m_dict))p1.start()p1.join()   #等p1进程结束,主进程继续执行print(f"主进程:{m_list}")print(f"主进程:{m_dict}")

进程池(Pool)

Python提供了更好的管理多个进程的方式,就是使用进程池
进程池可以提供指定数量的进程给用户使用,即当有新的请求提交到进程池中时,如果池未满,则会创建一个新的进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待,只要池中有进程空闲下来,该请求就能得到执行。
在这里插入图片描述

进程池使用案例

def func1(name):print(f"当前进程的ID:{os.getpid()},{name}")sleep(2)return namedef func2(args):print(args)if __name__ == "__main__":pool = Pool(5)pool.apply_async(func = func1,args=('sxt1',),callback=func2)pool.apply_async(func = func1,args=('sxt2',),callback=func2)pool.apply_async(func = func1,args=('sxt3',),callback=func2)pool.apply_async(func = func1,args=('sxt4',))pool.apply_async(func = func1,args=('sxt5',))pool.apply_async(func = func1,args=('sxt6',))pool.apply_async(func = func1,args=('sxt7',))pool.apply_async(func = func1,args=('sxt8',))pool.close()pool.join()

使用with管理进程池

def func1(name):print(f"当前进程的ID:{os.getpid()},{name}")sleep(2)return nameif __name__ == "__main__":with Pool(5) as pool:args = pool.map(func1,('sxt1,','sxt2,','sxt3,','sxt4,','sxt5,','sxt6,','sxt7,','sxt8,'))for a in args:print(a)

协程是什么

协程,Coroutines,也叫作纤程(Fiber)
协程,全称是“协同程序”,用来实现任务协作。是一种在线程中,比线程更加轻量级的存在,由程序员自己写程序来管理。
当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待的时间,提高了效率。一个故事说明进程、线程、协程的关系

乔布斯想开工厂生产手机,费劲力气,制作一条生产线,这个生产线上有很多的器件以及材料。一条生产线就是一个进程。
只有生产线是不够的,所以找五个工人来进行生产,这个工人能够利用这些材料最终一步步的将手机做出来,这五个工人就
是五个线程。
为了提高生产率,想到3种办法:
1 一条生产线上多招些工人,一起来做手机,这样效率是成倍増长,即单进程多线程方式
2 多条生产线,每个生产线上多个工人,即多进程多线程
乔布斯深入一线发现工人不是那么忙,有很多等待时间。于是规定:如果某个员工在等待生
产线某个零件生产时 ,不要闲着,干点其他工作。也就是说:如果一个线程等待某些条件,
可以充分利用这个时间去做其它事情,这就是:协程方式。

不使用协程执行多个任务

def func1():for i in range(3):print(f'北京:第{i}次打印啦')time.sleep(1)return "func1执行完毕"
def func2():for k in range(3):print(f'上海:第{k}次打印了' )time.sleep(1)return "func2执行完毕"def main():func1()func2()
if __name__ == '__main__':start_time = time.time()main()end_time = time.time()print(f"耗时{end_time-start_time}")   #不使用协程,耗时6秒

使用yield协程,实现任务切换

def func1():for i in range(3):print(f'北京:第{i}次打印啦')yield  # 只要方法包含了yield,就变成一个生成器time.sleep(1)
def func2():g = func1()    #func1是一个生成器,func1()就不会直接调用,需要通过next()print(type(g))for k in range(3):print(f'上海:第{k}次打印了' )next(g)   #继续执行func1的代码time.sleep(1)if __name__ == '__main__':#有了yield,我们实现了两个任务的切换+保存状态start_time = time.time()func2()end_time = time.time()print(f"耗时{end_time-start_time}")   #耗时5.0秒,效率差别不大

asyncio实现协程(重点)

  1. 常的函数执行时是不会中断的,所以你要写一个能够中断的函数,就需要加 async.async 用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件(假设挂起条件是 sleep(5) )消失后,也就是5秒到了再回来执行
  2. await 用来用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。
  3. asyncio 是python3.5之后的协程模块,是python实现并发重要的包,这个包使用事件循环驱动实现并发。
async def func1():     #async表示方法是异步的for i in range(3):print(f'北京:第{i}次打印啦')await asyncio.sleep(1)return "func1执行完毕"
async def func2():for k in range(3):print(f'上海:第{k}次打印了' )await asyncio.sleep(1)return "func2执行完毕"
async def main():res = await asyncio.gather(func1(), func2())#await异步执行func1方法#返回值为函数的返回值列表print(res)if __name__ == '__main__':start_time = time.time()asyncio.run(main())end_time = time.time()print(f"耗时{end_time-start_time}")   #耗时3秒,效率极大提高

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

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

相关文章

ChatGLM 本地部署指南(问题解决)

硬件要求&#xff08;模型推理&#xff09;&#xff1a; INT4 &#xff1a; RTX3090*1&#xff0c;显存24GB&#xff0c;内存32GB&#xff0c;系统盘200GB 如果你没有 GPU 硬件的话&#xff0c;也可以在 CPU 上进行推理&#xff0c;但是推理速度会更慢。 模型微调硬件要求更高。…

【双碳系列】碳中和、碳排放、温室气体、弹手指、碳储量、碳循环及leap、cge、dice、openLCA模型

气候变化是当前人类生存和发展所面临的共同挑战&#xff0c;受到世界各国人民和政府的高度关注 ①“双碳”目标下资源环境中的可计算一般均衡&#xff08;CGE&#xff09;模型实践技术应用 可计算一般均衡模型&#xff08;CGE模型&#xff09;由于其能够模拟宏观经济系统运行…

在论文写作中使用 LaTeX 生成算法伪代码

最近在论文写作中&#xff0c;我需要表示算法的逻辑。由于 Word 没有较好的模板&#xff0c;因此我选择使用 LaTeX 来生成算法伪代码&#xff0c;然后将其截图或转换为 SVG 格式&#xff0c;贴入论文中。 关于 LaTeX 的伪代码写作技巧&#xff0c;可以参考这篇文章&#xff1a…

OpenBayes 一周速览|Apple 开源大模型 OpenELM 上线;字节发布 COCONut 首个全景图像分割数据集,入选 CVPR2024

公共资源速递 This Weekly Snapshots &#xff01; 5 个数据集&#xff1a; * COCONut 大规模图像分割数据集 * THUCNews 新闻数据集 * DuConv 对话数据集 * 安徽电信知道问答数据集 * Sentiment Analysis 中文情感分析数据集 2 个模型&#xff1a; * OpenELM-3B-Inst…

前端组件库图片上传时候做自定义裁剪操作

不论是vue还是react项目&#xff0c;我们在使用antd组件库做上传图片的时候&#xff0c;有一个上传图片裁剪的功能&#xff0c;但是这个功能默认是只支持1:1的裁剪操作&#xff0c;如何做到自定义的裁剪操作&#xff1f;比如显示宽高比&#xff1f;是否可以缩放和旋转操作&…

【Redis】RDB持久化和AOF 持久化

分布式缓存 单点 Redis 的问题 数据丢失&#xff08;持久化&#xff09;并发能力不如集群&#xff08;主从集群、读写分离&#xff09;Redis宕机导致服务不可用&#xff08;Redis哨兵&#xff09;存储能力差&#xff08;分片集群&#xff09; Redis 持久化 RDB 持久化 什么…

力扣hot100:199. 二叉树的右视图/437. 路径总和 III(dfs/回溯/树上前缀和/哈希表)

文章目录 一、LeetCode&#xff1a;199. 二叉树的右视图二、LeetCode&#xff1a;437. 路径总和 III 一、LeetCode&#xff1a;199. 二叉树的右视图 LeetCode&#xff1a;199. 二叉树的右视图 差点因为是个中等题打退堂鼓。其实比较简单。 右视图实际上只需要找到&#xff0c…

python自动化生成ppt

使用Python和python-pptx创建PPT 在这篇博客中&#xff0c;我们将探讨如何使用Python库python-pptx来创建一个简单的PowerPoint演示文稿&#xff08;PPT&#xff09;。这个库允许我们以编程方式创建幻灯片、添加文本、图片、表格和自定义形状。 安装python-pptx 首先&#x…

Relaxed MemoryConsistency

SC和TSO都被称之为强&#xff08;strong&#xff09;保序模型&#xff1b; because the global memory order of each model usually respects (preserves) per-thread program order&#xff1b;回想一下&#xff0c;对于load和store的所有四种组合&#xff08;Load -> Lo…

六一儿童节活动方案策划怎么写?

六一儿童节活动方案策划不难&#xff0c;一般看前人策划的案例就可以仿写一篇充满创意的儿童节活动方案。 当然&#xff0c;你也可以照着下面的模版直接写&#xff1a; 成年人的时间是离弦的箭 向着目标,一往无前 孩子的时间是旋转木马 载着今天和明天转啊转啊圈圈 成年人…

基于FPGA的视频矩阵 视频拼接 无缝切换解决方案

视频矩阵 视频矩阵 视频拼接 无缝切换 1. 最大支持144路HDMI视频输入&#xff0c;最大支持144路路HDMI输出&#xff0c;完全交叉切换。 2. 与包括1080p/60的所有HDTV分辨率和高达1920*1200的PC的分辨率兼容&#xff1b; 3. 支持HDMI 1.3a、HDCP 1.3、HDCP 1.4、以及DVI 1.0协…

教你解决PUBG绝地求生游戏中闪退掉线无法重连回去的问题

《绝地求生》&#xff08;PUBG&#xff09;&#xff0c;作为一款在全球范围内掀起热潮的战术竞技游戏&#xff0c;以其栩栩如生的战场环境和令人心跳加速的生存冒险博得了广大玩家的青睐。然而&#xff0c;一些玩家在经历了一场惊心动魄的对局后&#xff0c;却面临了一个不大不…

django显示网页步骤

显示网页步骤 小白的django学习笔记 2024/5/6 8:30 文章目录 显示网页步骤创建输入框&#xff08;文本、单选、多选&#xff09;效果如何在django中显示网页写函数配置地址运行&#xff0c;要选择这个工程名的&#xff0c;使用socket复制ip&#xff0c;后面在加上名字,成功&…

Nextcloud私有云盘-重新定义云存储体验

Nextcloud私有云盘-重新定义云存储体验 1. 什么是Nextcloud ​ Nextcloud是一个开源的云存储和协作平台&#xff0c;旨在为个人用户、企业和团队提供安全、隐私保护的数据存储和共享解决方案。它允许您在不同设备之间同步、共享文件&#xff0c;提供了强大的协作工具和应用生…

数据库入门(sql文档+命令行)

一.基础知识 1.SQL&#xff08;Structured Query Language&#xff09;结构化查询语言分类&#xff1a; DDL数据定义语言用来定义数据库对象&#xff1a;数据库、表、字段DML数据操作语言对数据库进行增删改查DQL数据查询语言查询数据库中表的信息DCL数据控制语言用来创建数据…

服务器托管与租赁的有什么区别

服务器作为企业数据存储、应用部署的重要工具&#xff0c;其选择方式多种多样。其中&#xff0c;服务器托管和租赁是两种常见的形式。 在选择服务器时&#xff0c;很多企业会面临一个问题&#xff1a;是选择托管服务还是租赁服务器&#xff1f; 一、什么是服务器租用和服务器托…

鸿蒙——即将是国内全部物联网的搭载系统

国内物联网时代 中国国内物联网时代是指在中国国内&#xff0c;物联网&#xff08;Internet of Things&#xff0c;简称IoT&#xff09;技术得到广泛应用和发展的时代。在这个时代&#xff0c;各种设备和物品都可以通过互联网进行连接和交互&#xff0c;实现信息的采集、传输和…

【SpringBoot整合系列】SpringBoot整合RabbitMQ-消息可靠性

目录 确保消息的可靠性RabbitMQ 消息发送可靠性分析解决方案开启事务机制发送方确认机制单条消息处理消息批量处理 失败重试自带重试机制业务重试 RabbitMQ 消息消费可靠性如何保证消息在队列RabbitMQ 的消息消费&#xff0c;整体上来说有两种不同的思路&#xff1a;确保消费成…

让GPT们成为我们的小助手:使用ChatGPT来生成测试用数据

让GPT们成为我们的小助手 任务&#xff1a;帮忙生成测试数据 今天本来想做一个测试&#xff0c;所以需要一些测试数据。为了让测试显得更真实&#xff0c;所以希望测试数据看上去就是一份真实的数据&#xff0c;所以我就希望ChatGPT&#xff08;这里是代指&#xff0c;我有使…

rider自定义代码片段(以C#为例)

1.先看效果 2.在哪设置 File→Settings→Editor→Live Templates→C#3.咋定义 代码片段中的变量用$$包围&#xff0c;而且我们可以自定义变量名称&#xff0c;如CName。选择我们自定义的变量名称我们可以修改变量是否可以被修改以及变量将自动匹配的值。 比如将CName自动填充…