【Python进阶笔记】md文档笔记第6篇:Python进程和多线程使用(图文和代码)

本文从14大模块展示了python高级用的应用。分别有Linux命令,多任务编程、网络编程、Http协议和静态Web编程、html+css、JavaScript、jQuery、MySql数据库的各种用法、python的闭包和装饰器、mini-web框架、正则表达式等相关文章的详细讲述。

全套md格式笔记和代码自取: 请移步这里


共 14 章,157 子模块,总计 85313 字


进程的注意点

学习目标

  • 能够说出进程的注意点

1. 进程的注意点介绍

  1. 进程之间不共享全局变量
  2. 主进程会等待所有的子进程执行结束再结束

2. 进程之间不共享全局变量

import multiprocessing
import time# 定义全局变量g_list = list()# 添加数据的任务def add_data():for i in range(5):g_list.append(i)print("add:", i)time.sleep(0.2)# 代码执行到此,说明数据添加完成print("add_data:", g_list)def read_data():print("read_data", g_list)if __name__ == '__main__':# 创建添加数据的子进程add_data_process = multiprocessing.Process(target=add_data)# 创建读取数据的子进程read_data_process = multiprocessing.Process(target=read_data)# 启动子进程执行对应的任务add_data_process.start()# 主进程等待添加数据的子进程执行完成以后程序再继续往下执行,读取数据add_data_process.join()read_data_process.start()print("main:", g_list)# 总结: 多进程之间不共享全局变量

执行结果:

add: 0
add: 1
add: 2
add: 3
add: 4
add_data: [0, 1, 2, 3, 4]
main: []
read_data []

进程之间不共享全局变量的解释效果图:

进程关系

3. 进程之间不共享全局变量的小结

  • 创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。

4. 主进程会等待所有的子进程执行结束再结束

假如我们现在创建一个子进程,这个子进程执行完大概需要2秒钟,现在让主进程执行0.5秒钟就退出程序,查看一下执行结果,示例代码如下:

import multiprocessing
import time# 定义进程所需要执行的任务def task():for i in range(10):print("任务执行中...")time.sleep(0.2)if __name__ == '__main__':# 创建子进程sub_process = multiprocessing.Process(target=task)sub_process.start()# 主进程延时0.5秒钟time.sleep(0.5)print("over")exit()# 总结: 主进程会等待所有的子进程执行完成以后程序再退出

执行结果:

任务执行中...
任务执行中...
任务执行中...
over
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...
任务执行中...

说明:

通过上面代码的执行结果,我们可以得知: 主进程会等待所有的子进程执行结束再结束

假如我们就让主进程执行0.5秒钟,子进程就销毁不再执行,那怎么办呢?

  • 我们可以设置守护主进程 或者 在主进程退出之前 让子进程销毁

守护主进程:

  • 守护主进程就是主进程退出子进程销毁不再执行

子进程销毁:

  • 子进程执行结束

保证主进程正常退出的示例代码:

import multiprocessing
import time# 定义进程所需要执行的任务def task():for i in range(10):print("任务执行中...")time.sleep(0.2)if __name__ == '__main__':# 创建子进程sub_process = multiprocessing.Process(target=task)# 设置守护主进程,主进程退出子进程直接销毁,子进程的生命周期依赖与主进程# sub_process.daemon = Truesub_process.start()time.sleep(0.5)print("over")# 让子进程销毁sub_process.terminate()exit()# 总结: 主进程会等待所有的子进程执行完成以后程序再退出# 如果想要主进程退出子进程销毁,可以设置守护主进程或者在主进程退出之前让子进程销毁

执行结果:

任务执行中...
任务执行中...
任务执行中...
over

5. 主进程会等待所有的子进程执行结束再结束的小结

  • 为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行
  • 设置守护主进程方式: 子进程对象.daemon = True
  • 销毁子进程方式: 子进程对象.terminate()

线程

学习目标

能够知道线程的作用


1. 线程的介绍

在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。

2. 线程的概念

线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度 ,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

3. 线程的作用

多线程可以完成多任务

多线程效果图:

线程

4. 小结

  • 线程是Python程序中实现多任务的另外一种方式,线程的执行需要cpu调度来完成。

多线程的使用

学习目标

  • 能够使用多线程完成多任务

1. 导入线程模块

  #导入线程模块import threading

2. 线程类Thread参数说明

Thread([group [, target [, name [, args [, kwargs]]]]])

  • group: 线程组,目前只能使用None
  • target: 执行的目标任务名
  • args: 以元组的方式给执行任务传参
  • kwargs: 以字典方式给执行任务传参
  • name: 线程名,一般不用设置

3. 启动线程

启动线程使用start方法

4. 多线程完成多任务的代码

import threading
import time# 唱歌任务def sing():# 扩展: 当前线程# print("sing当前执行的线程为:", threading.current_thread())for i in range(3):print("正在唱歌...%d" % i)time.sleep(1)# 跳舞任务def dance():# 扩展: 当前线程# print("dance当前执行的线程为:", threading.current_thread())for i in range(3):print("正在跳舞...%d" % i)time.sleep(1)if __name__ == '__main__':# 扩展: 当前线程# print("当前执行的线程为:", threading.current_thread())# 创建唱歌的线程# target: 线程执行的函数名sing_thread = threading.Thread(target=sing)# 创建跳舞的线程dance_thread = threading.Thread(target=dance)# 开启线程sing_thread.start()dance_thread.start()

执行结果:

正在唱歌...0
正在跳舞...0
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2

5. 小结

  1. 导入线程模块

    • import threading
  2. 创建子线程并指定执行的任务

    • sub_thread = threading.Thread(target=任务名)
  3. 启动线程执行任务

    • sub_thread.start()

线程执行带有参数的任务

学习目标

  • 能够写出线程执行带有参数的任务

1. 线程执行带有参数的任务的介绍

前面我们使用线程执行的任务是没有参数的,假如我们使用线程执行的任务带有参数,如何给函数传参呢?

Thread类执行任务并给任务传参数有两种方式:

  • args 表示以元组的方式给执行任务传参
  • kwargs 表示以字典方式给执行任务传参

2. args参数的使用

示例代码:

import threading
import time# 带有参数的任务def task(count):for i in range(count):print("任务执行中..")time.sleep(0.2)else:print("任务执行完成")if __name__ == '__main__':# 创建子线程# args: 以元组的方式给任务传入参数sub_thread = threading.Thread(target=task, args=(5,))sub_thread.start()

执行结果:

任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行中..
任务执行完成

3. kwargs参数的使用

示例代码:

import threading
import time# 带有参数的任务def task(count):for i in range(count):print("任务执行中..")time.sleep(0.2)else:print("任务执行完成")if __name__ == '__main__':# 创建子线程# kwargs: 表示以字典方式传入参数sub_thread = threading.Thread(target=task, kwargs={"count": 3})sub_thread.start()

执行结果:

任务执行中..
任务执行中..
任务执行中..
任务执行完成

4. 小结

  • 线程执行任务并传参有两种方式:

    • 元组方式传参(args) :元组方式传参一定要和参数的顺序保持一致。
    • 字典方式传参(kwargs):字典方式传参字典中的key一定要和参数名保持一致。

线程的注意点

学习目标

  • 能够说出线程的注意点

1. 线程的注意点介绍

  1. 线程之间执行是无序的
  2. 主线程会等待所有的子线程执行结束再结束
  3. 线程之间共享全局变量
  4. 线程之间共享全局变量数据出现错误问题

2. 线程之间执行是无序的

import threading
import timedef task():time.sleep(1)print("当前线程:", threading.current_thread().name)if __name__ == '__main__':for _ in range(5):sub_thread = threading.Thread(target=task)sub_thread.start()

执行结果:

当前线程: Thread-1
当前线程: Thread-2
当前线程: Thread-4
当前线程: Thread-5
当前线程: Thread-3

说明:

  • 线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
  • 进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。

3. 主线程会等待所有的子线程执行结束再结束

假如我们现在创建一个子线程,这个子线程执行完大概需要2.5秒钟,现在让主线程执行1秒钟就退出程序,查看一下执行结果,示例代码如下:

import threading
import time# 测试主线程是否会等待子线程执行完成以后程序再退出def show_info():for i in range(5):print("test:", i)time.sleep(0.5)if __name__ == '__main__':sub_thread = threading.Thread(target=show_info)sub_thread.start()# 主线程延时1秒time.sleep(1)print("over")

执行结果:

test: 0
test: 1
over
test: 2
test: 3
test: 4

说明:

通过上面代码的执行结果,我们可以得知: 主线程会等待所有的子线程执行结束再结束

假如我们就让主线程执行1秒钟,子线程就销毁不再执行,那怎么办呢?

  • 我们可以设置守护主线程

守护主线程:

  • 守护主线程就是主线程退出子线程销毁不再执行

设置守护主线程有两种方式:

  1. threading.Thread(target=show_info, daemon=True)
  2. 线程对象.setDaemon(True)

设置守护主线程的示例代码:

import threading
import time# 测试主线程是否会等待子线程执行完成以后程序再退出def show_info():for i in range(5):print("test:", i)time.sleep(0.5)if __name__ == '__main__':# 创建子线程守护主线程 # daemon=True 守护主线程# 守护主线程方式1sub_thread = threading.Thread(target=show_info, daemon=True)# 设置成为守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码# 守护主线程方式2# sub_thread.setDaemon(True)sub_thread.start()# 主线程延时1秒time.sleep(1)print("over")

执行结果:

test: 0
test: 1
over

3. 线程之间共享全局变量

需求:

  1. 定义一个列表类型的全局变量
  2. 创建两个子线程分别执行向全局变量添加数据的任务和向全局变量读取数据的任务
  3. 查看线程之间是否共享全局变量数据
import threading
import time# 定义全局变量my_list = list()# 写入数据任务def write_data():for i in range(5):my_list.append(i)time.sleep(0.1)print("write_data:", my_list)# 读取数据任务def read_data():print("read_data:", my_list)if __name__ == '__main__':# 创建写入数据的线程write_thread = threading.Thread(target=write_data)# 创建读取数据的线程read_thread = threading.Thread(target=read_data)write_thread.start()# 延时# time.sleep(1)# 主线程等待写入线程执行完成以后代码在继续往下执行write_thread.join()print("开始读取数据啦")read_thread.start()

执行结果:

write_data: [0, 1, 2, 3, 4]
开始读取数据啦
read_data: [0, 1, 2, 3, 4]

4. 线程之间共享全局变量数据出现错误问题

需求:

  1. 定义两个函数,实现循环100万次,每循环一次给全局变量加1
  2. 创建两个子线程执行对应的两个函数,查看计算后的结果
import threading# 定义全局变量g_num = 0# 循环一次给全局变量加1def sum_num1():for i in range(1000000):global g_numg_num += 1print("sum1:", g_num)# 循环一次给全局变量加1def sum_num2():for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)if __name__ == '__main__':# 创建两个线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)# 启动线程first_thread.start()# 启动线程second_thread.start()

执行结果:

sum1: 1210949
sum2: 1496035

注意点:

多线程同时对全局变量操作数据发生了错误

错误分析:

两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:

  1. 在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0
  2. 然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1
  3. 然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
  4. 这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1

全局变量数据错误的解决办法:

线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机

线程同步的方式:

  1. 线程等待(join)
  2. 互斥锁

线程等待的示例代码:

import threading# 定义全局变量g_num = 0# 循环1000000次每次给全局变量加1def sum_num1():for i in range(1000000):global g_numg_num += 1print("sum1:", g_num)# 循环1000000次每次给全局变量加1def sum_num2():for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)if __name__ == '__main__':# 创建两个线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)# 启动线程first_thread.start()# 主线程等待第一个线程执行完成以后代码再继续执行,让其执行第二个线程# 线程同步: 一个任务执行完成以后另外一个任务才能执行,同一个时刻只有一个任务在执行first_thread.join()# 启动线程second_thread.start()

执行结果:

sum1: 1000000
sum2: 2000000

5. 小结

  • 线程执行执行是无序的

  • 主线程默认会等待所有子线程执行结束再结束,设置守护主线程的目的是主线程退出子线程销毁。

  • 线程之间共享全局变量,好处是可以对全局变量的数据进行共享。

  • 线程之间共享全局变量可能会导致数据出现错误问题,可以使用线程同步方式来解决这个问题。

    • 线程等待(join)

互斥锁

学习目标

  • 能够知道互斥锁的作用

1.互斥锁的概念

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

注意:

  • 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。

为了更好的理解互斥锁,请看下面的图:

互斥锁

3. 互斥锁的使用

threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以一把互斥锁。

互斥锁使用步骤:

  # 创建锁mutex = threading.Lock()# 上锁mutex.acquire()...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...# 释放锁mutex.release()

注意点:

  • acquire和release方法之间的代码同一时刻只能有一个线程去操作
  • 如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。

4. 使用互斥锁完成2个线程对同一个全局变量各加100万次的操作

import threading# 定义全局变量g_num = 0# 创建全局互斥锁lock = threading.Lock()# 循环一次给全局变量加1def sum_num1():# 上锁lock.acquire()for i in range(1000000):global g_numg_num += 1print("sum1:", g_num)# 释放锁lock.release()# 循环一次给全局变量加1def sum_num2():# 上锁lock.acquire()for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)# 释放锁lock.release()if __name__ == '__main__':# 创建两个线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)# 启动线程first_thread.start()second_thread.start()# 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待# 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行

执行结果:

sum1: 1000000
sum2: 2000000

说明:

通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题

5. 小结

  • 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
  • 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
  • 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
  • 互斥锁如果没有使用好容易出现死锁的情况

未完待续 下一期下一章

全套笔记直接地址: 请移步这里

reading.Lock()

循环一次给全局变量加1

def sum_num1():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1

print("sum1:", g_num)
# 释放锁
lock.release()

循环一次给全局变量加1

def sum_num2():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num += 1
print(“sum2:”, g_num)
# 释放锁
lock.release()

if name == ‘main’:
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动线程
first_thread.start()
second_thread.start()

# 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待
# 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行
**执行结果:**```py
sum1: 1000000
sum2: 2000000

说明:

通过执行结果可以地址互斥锁能够保证多个线程访问共享数据不会出现数据错误问题

5. 小结

  • 互斥锁的作用就是保证同一时刻只能有一个线程去操作共享数据,保证共享数据不会出现错误问题
  • 使用互斥锁的好处确保某段关键代码只能由一个线程从头到尾完整地去执行
  • 使用互斥锁会影响代码的执行效率,多任务改成了单任务执行
  • 互斥锁如果没有使用好容易出现死锁的情况

未完待续 下一期下一章

全套笔记直接地址: 请移步这里

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

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

相关文章

yolov5从英伟达平台移植到华为昇腾开发板上的思路

作者:朱金灿 来源:clever101的专栏 为什么大多数人学不会人工智能编程?>>> 最近需要将yolov5代码从英伟达平台移植到华为昇腾开发板上。搜了一些代码和资料,大致明白了二者的差别。 1.二者使用的模型文件不一样 yolov…

软件测试职业规划导图

公司开发的产品专业性较强,软件测试人员需要有很强的专业知识,现在软件测试人员发展出现了一种测试管理者不愿意看到的景象: 1、开发技术较强的软件测试人员转向了软件开发(非测试工具开发); 2、业务能力较强的测试人员转向了软件…

MFC设置单选按钮点击自己可以可选和不可选

mfc是c的一个框架,可谓是经久不衰。最近博主遇到一个问题,就是单选按钮点击自己可以设置可选和不可选,貌似类似复选框一样,但领导分发的任务上要求的是用单选按钮实现复选框这种类似功能,实现效果类似如下图&#xff1…

TYPE-C、PD原理

一、Type-C简介以及历史 自1998年以来,USB发布至今,USB已经走过20个年头有余了。在这20年间,USB-IF组织发布N种接口状态,包括A口、B口、MINI-A、MINI-B、Micro-A、Micro-B等等接口形态,由于各家产品的喜好不同&#x…

城市数字孪生优秀案例集 - 城市治理类 - 深圳市城市交通数字孪生建设

一、背景意义 “十四五”规划、《数字交通发展规划纲要》、《广东省数字经济促进条例》等提出“构建城市数据资源 体系,推进城市数据大脑建设,探索建设数字孪生城市”。 当前,我国 9 亿城市化人口每天出行约 16 亿人 次,主要大城…

打破障碍:2024年赋能企业人工智慧生成创新-5个应用场景与6个转型步骤

想要了解如何采用生成式AI来提高企业效率和竞争力?本指南将介绍如何采用生成式AI来实现数字化转型,并打造智能化商业模式。从5大应用场景和6大步骤切入,让您了解如何开启生成式AI创新。立即连线创新专家咨询或观看创新战略方案视频进一步了解…

现代图标集wxArtProvider发布 —— 发布于2023年11月21日

Perazz发布了wxMaterialDesignArtProvider,这是一个自定义的wxArtProvider类,从MaterialDesign、SimpleIcons、FontAwesome和FluentUI系统数据集中提供基于SVG的图标。所有这些数据集都有许可证(MIT、CC BY 4.0、CC0 1.0、Apache 2.0&#xf…

【Python自学】七个超强学习网站,你值得拥有!

学习Python最主要的还是要动手,去找一些自己感兴趣的脚本,代码去练习,练的越多,对于一些英语单词,特殊符号要比死记硬背要容易记得些。 以下这些网站,虽说不上全方位的满足你的需求,但是大部分也…

基于M估计样本一致性算法的点云平面拟合

平面拟合 1、算法简介2、参考文献3、实现效果4、相关代码 1、算法简介 RANSAC 是在给定模型和距离阈值 T T T的情况下,通过寻找最小代价 C C C来确定内点数据并拟合模型。如式(1)所示的代价函数,当点到模型的距离 e e e小于阈值 T…

RabbitMQ之MQ的可靠性

文章目录 前言一、数据持久化交换机持久化队列持久化消息持久化 二、LazyQueue控制台配置Lazy模式代码配置Lazy模式更新已有队列为lazy模式 总结 前言 消息到达MQ以后,如果MQ不能及时保存,也会导致消息丢失,所以MQ的可靠性也非常重要。 一、…

(4)BUUCTF-web-[极客大挑战 2019]EasySQL1

前言: 觉得这个题目挺有意义的,因为最近在学数据库,但是不知道在现实中有什么应用,所以学起来也没有什么兴趣,做了这个题目,发现数据库还是挺有用处的,哈哈 知识点: mysql 中and和…

【从浅识到熟知Linux】基本指定之zip、unzip和tar

🎈归属专栏:从浅学到熟知Linux 🚗个人主页:Jammingpro 🐟每日一句:周五写博客更刺激了,想到明天可以晚起床半小时,瞬间精神抖擞。再写它10篇博客。 文章前言:本文介绍zip…