原文件下载路径:
基于Python的并发编程技术详解:进程与线程的应用及优化策略
作业答案及解析:
day22-并发编程(上)作业答案
day22 并发编程(上)
网络编程,了解网络相关的知识点并且要知道几乎所有网络的通信本质上都是通过socket模块实现。例如:网站、网络爬虫。
并发编程,提升代码执行的效率。原来代码执行需要20分钟,学习并发编程后可以加快到1分钟执行完毕。
今日课程目标:初步了解进程和线程并可以基于线程实现并发编程。
今日概要:
- 初识进程和线程
- 多线程开发
- 线程安全
- 线程锁
- 死锁
- 线程池
1. 进程和线程
先来了解下进程和线程。
类比:
-
一个工厂,至少有一个车间,一个车间中至少有一个工人,最终是工人在工作。
-
一个程序,至少有一个进程,一个进程中至少有一个线程,最终是线程在工作。
上述串行的代码示例就是一个程序,在使用python xx.py 运行时,内部就创建一个进程(主进程),在进程中创建了一个线程(主线程),由线程逐行运行代码。
进程和线程:
线程,是计算机中可以被cpu调度的最小单元(真正在工作)。
进程,是计算机资源分配的最小单元(进程为线程提供资源)。
一个进程中可以有多个线程,同一个进程中的线程可以共享此进程中的资源。
以前我们开发的程序中所有的行为都只能通过串行的形式运行,排队逐一执行,前面未完成,后面也无法继续。例如:
import time
result = 0
for i in range(100000000):
result += i
print(result)
import time
import requests
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
print(time.time())
for file_name, url in url_list:
res = requests.get(url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(file_name, time.time())
通过 进程 和 线程 都可以将 串行
的程序变为并发
,对于上述示例来说就是同时下载三个视频,这样很短的时间内就可以下载完成。
1.1 多线程
基于多线程对上述串行示例进行优化:
- 一个工厂,创建一个车间,这个车间中创建 3个工人,并行处理任务。
- 一个程序,创建一个进程,这个进程中创建 3个线程,并行处理任务。
import time
import requests
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
for file_name, url in url_list:
res = requests.get(url)
with open(file_name, mode='wb') as f:
f.write(res.content)
import time
import requests
import threading
"""
def func(a1,a2,a3):
pass
t = threaing.Thread(target=func,args=(11,22,33))
t.start()
"""
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
def task(file_name, video_url):
res = requests.get(video_url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(time.time())
for name, url in url_list:
# 创建线程,让每个线程都去执行task函数(参数不同)
t = threading.Thread(target=task, args=(name, url))
t.start()
1.2 多进程
基于多进程对上述串行示例进行优化:
- 一个工厂,创建 三个车间,每个车间 一个工人(共3人),并行处理任务。
- 一个程序,创建 三个进程,每个进程 一个线程(共3人),并行处理任务。
import time
import requests
import multiprocessing
# 进程创建之后,在进程中还会创建一个线程。
# t = multiprocessing.Process(target=函数名, args=(name, url))
# t.start()
url_list = [
("东北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
("卡特扣篮.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
("罗斯mvp.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f240000buuer5aa4tij4gv6ajqg")
]
def task(file_name, video_url):
res = requests.get(video_url)
with open(file_name, mode='wb') as f:
f.write(res.content)
print(time.time())
if __name__ == '__main__':
print(time.time())
for name, url in url_list:
t = multiprocessing.Process(target=task, args=(name, url))
t.start()
综上所述,大家会发现 多进程 的开销比 多线程 的开销大。哪是不是使用多线程要比多进程更好呀?
接下来,给大家再来介绍一个Python内置的GIL锁的知识,然后再根据 进程 和 线程 各自的特点总结各自适合应用场景。
1.3 GIL锁
GIL, 全局解释器锁(Global Interpreter Lock),是CPython解释器特有一个玩意,让一个进程中同一个时刻只能有一个线程可以被CPU调用。
如果程序想利用 计算机的多核优势,让CPU同时处理一些任务,适合用多进程开发(即使资源开销大)。
如果程序不利用 计算机的多核优势,适合用多线程开发。
常见的程序开发中,计算操作需要使用CPU多核优势,IO操作不需要利用CPU的多核优势,所以,就有这一句话:
- 计算密集型,用多进程,例如:大量的数据计算【累加计算示例】。
- IO密集型,用多线程,例如:文件读写、网络数据传输【下载抖音视频示例】。
累加计算示例(计算密集型):
-
串行处理
import time start = time.time() result = 0 for i in range(100000000): result += i print(result) end = time.time() print("耗时:", end - start) # 耗时: 9.522780179977417
-
多进程处理
import time import multiprocessing def task(start, end, queue): result = 0 for i in range(start, end): result += i queue.put(result) if __name__ == '__main__': queue = multiprocessing.Queue() start_time = time.time() p1 = multiprocessing.Process(target=task, args=(0, 50000000, queue)) p1.start() p2 = multiprocessing.Process(target=task, args=(50000000, 100000000, queue)) p2.start() v1 = queue.get(block=True) #阻塞 v2 = queue.get(block=True) #阻塞 print(v1 + v2) end_time = time.time() print("耗时:", end_time - start_time) # 耗时: 2.6232550144195557
当然,在程序开发中 多线程 和 多进程 是可以结合使用,例如:创建2个进程(建议与CPU个数相同),每个进程中创建3个线程。
import multiprocessing
import threading
def thread_task():
pass
def task(start, end):
t1 = threading.Thread(target=thread_task)
t1.start()
t2 = threading.Thread(target=thread_task)
t2.start()
t3 = threading.Thread(target=thread_task)
t3.start()
if __name__ == '__main__':
p1 = multiprocessing.Process(target=task, args=(0, 50000000))
p1.start()
p2 = multiprocessing.Process(target=task, args=(50000000, 100000000))
p2.start()
2. 多线程开发
import threading
def task(arg):
pass
# 创建一个Thread对象(线程),并封装线程被CPU调度时应该执行的任务和相关参数。
t = threading.Thread(target=task,args=('xxx',)