对于进程与线程,我们可以简单类比为:
一个工厂,至少要有一个车间,一个车间至少有一个工人,工人在工作->工厂运行
一个程序,至少要有一个进程,一个进程至少有一个线程,线程在工作->程序运行
提高效率方式:
增加线程:
一个工厂,一个车间,一个车间两个工人,工人多了->工厂效率提高
一个程序,一个进程,一个进程两个线程,线程多了->程序效率提高
增加进程:
一个工厂,两个车间,每一个车间一个工人,车间多了->工厂效率提高
一个程序,两个进程,每一个进程一个线程,进程多了->程序效率提高
默认情况下,我们开发的程序都是通过串行的形式运行的,排队逐一执行,前面未运行完成,后面的无法直接运行,如:
import time
number1 = 100000000
number2 = 200000000
time1 = time.time()
result = 0
for i in range(number1):
result += 1
print(result)
result = 0
for i in range(number2):
result += 1
print(result)
time2 = time.time()
print(f'耗时{time2-time1}秒')
#运行结果:
#100000000
#200000000
#耗时20.385773420333862秒
串行:任务按严格顺序依次执行,前一个任务完成后才能开始下一个任务。
并行:多个任务物理上同时执行,需依赖多核CPU或多处理器系统
并发:多个任务逻辑上同时执行,通过时间片轮转快速切换,宏观看似并行,微观仍是串行
通过进程和线程,我们可以将串行程序变为并发程序
增加线程:
一个工厂,一个车间,一个车间两个工人,工人多了->工厂效率提高
一个程序,一个进程,一个进程两个线程,线程多了->程序效率提高
import time
import threading
def task(number):
result = 0
for i in range(number):
result += 1
print(result)
time1 = time.time()
#创建线程
numbers = [100000000,200000000]
threads = [] #记录线程
for num in numbers:
t = threading.Thread(target=task, args=(num,)) #创建多线程,target为运行的进程函数,args为传参(单个参数记得加逗号)
t.start() #启动线程
threads.append(t)
for t in threads:
t.join() #阻塞主线程,强制主线程等待所有子线程完成
time2 = time.time()
print(f'多线程方式,耗时{time2-time1}秒')
运行结果:
100000000
200000000
多线程方式,耗时8.283299684524536秒
增加进程:
一个工厂,两个车间,每一个车间一个工人,车间多了->工厂效率提高
一个程序,两个进程,每一个进程一个线程,进程多了->程序效率提高
import time
import multiprocessing
def task(number):
result = 0
for i in range(number):
result += 1
print(result)
if __name__ == '__main__':
time1 = time.time()
#创建进程
numbers = [100000000,200000000]
process = [] #记录进程
for num in numbers:
p = multiprocessing.Process(target=task, args=(num,)) #创建多进程,target为运行的进程函数,args为传参(单个参数记得加逗号)
p.start() #启动进程
process.append(p)
for p in process:
p.join() #阻塞主线程,强制主线程等待所有子进程完成
time2 = time.time()
print(f'多进程方式,耗时{time2-time1}秒')
运行结果:
100000000
200000000
多进程方式,耗时6.042091131210327秒
GIL,全局解释锁,是CPython解释器特有的,让一个进程中的同一时刻只能有一个线程可以被CPU调度
所以如果程序需利用计算机多核优势,让CPU同时处理一些任务,适合用多进程开发(资源开销大)
若程序不利用计算机多核优势,适合多线程开发
那么我们什么时候用多进程,什么时候用多线程呢?
IO密集型:较少使用计算机CPU资源用多线程,例如:文件读写,网络数据传输
计算密集型:以计算为主用多进程,例如大量的数据计算,占用资源比较多的
下载百度小说中的《西游记》
import requests
import threading
import json
def down(title,cid):
data = {
"book_id": "4306063500",
"cid": f"4306063500|{cid}",
"need_bookinfo": 1
}
data_str = json.dumps(data)
down_url = f'https://dushu.baidu.com/api/pc/getChapterContent?data={data_str}'
down_resp = requests.get(down_url)
dics = down_resp.json()
dic = dics['data']['novel']['content']
f = open(f"西游记/{title}.txt", mode="w", encoding="utf-8")
f.write(dic)
print(title,cid)
url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"}'
resp = requests.get(url)
data = resp.json()
data = data['data']['novel']['items']
threads = []
for i in data:
title = i['title']
cid = i['cid']
t = threading.Thread(target=down, args=(title, cid))
t.start()
threads.append(t)
for t in threads:
t.join()
print('over')
import time
import multiprocessing
def task(number,queue):
result = 0
for i in range(number):
result += 1
queue.put(result)
if __name__ == '__main__':
time1 = time.time()
queue = multiprocessing.Queue()
#创建进程
numbers = [100000000,100000,7,200000000,100000,10,300000000,500,10,20,7,4]
process = [] #记录进程
for num in numbers:
p = multiprocessing.Process(target=task, args=(num,queue))
p.start() #启动线程
process.append(p)
for p in process:
p.join() #阻塞主线程,强制主线程等待所有子进程完成
numsum = 0
while not queue.empty():
numsum += queue.get()
print(f'数组和为:{numsum}')
time2 = time.time()
print(f'耗时{time2-time1}')
t.start() 用于启动一个新线程,使其进入就绪状态,等待系统调度执行。
t.join() 使主线程等待目标子线程t执行完毕后再继续执行。
t.daemon 用于设置线程的守护属性(必须放在start之前),决定线程是否随主线程退出而强制终止。 例:t.daemon = False (默认值,不强制终止)
t.name 为子线程设置名字(放在start之前)。 例:t.name = 't1'
import time
import threading
def task(number):
print("当前线程的名称:",threading.current_thread().name)
result = 0
for i in range(number):
result += 1
print(result)
time1 = time.time()
#创建线程
numbers = [100000000,200000000]
threads = [] #记录线程
for num in numbers:
t = threading.Thread(target=task, args=(num,)) #创建多线程,target为运行的进程函数,args为传参(单个参数记得加逗号)
t.name = 't'+str(num) #线程命名
t.daemon = False #不随主流程终止
t.start() #启动线程
threads.append(t)
for t in threads:
t.join() #阻塞主线程,强制主线程等待所有子线程完成
time2 = time.time()
print(f'多线程方式,耗时{time2-time1}秒')
通过自定义线程类,可直接将线程需要做的事写到run方法中
import time
import threading
class MyThread(threading.Thread):
def run(self):
print('执行次线程',self._args)
num, = self._args
nsum = 0
for i in range(num):
nsum += 1
print(f'累加和为{nsum}')
t = MyThread(args=(100,))
t.start()
一个进程可以有多个线程,多个线程共享一个资源
多个线程同时去操作一个'变量'可能会存在数据混乱的情况
所以为了保证线程安全,可使用线程锁
创建锁:lock = threading.RLock()
申请锁:lock.acquire()
释放锁:lock.release()
import threading
import time
num = 500000000
result = 0
lock = threading.RLock() #创建锁
time1 = time.time()
def Add(number):
lock.acquire() #申请锁
global result
for i in range(number):
result += 1
lock.release() #释放锁
def Sub(number):
lock.acquire() #申请锁
global result
for i in range(number):
result -= 1
lock.release() #释放锁
t1 = threading.Thread(target=Add, args=(num,))
t2 = threading.Thread(target=Sub, args=(num,))
t1.start()
t2.start()
t1.join()
t2.join()
time2 = time.time()
print(result)
print(f'耗时{time2-time1}')
程序中,线程并不是越多越好的,开的线程多了可能会导致系统的性能更低,所以使用线程池是很有必要的
pool = ThreadPoolExecutor(10) 创建线程池,最多维护10个线程
pool.submit(fun, arg) 提交一个线程至线程池,若线程池中有空余线程即运行该线程,反之则等待线程池空缺
pool.shutdown(True) 等待线程池中的任务全部执行完毕
import time
from concurrent.futures import ThreadPoolExecutor
def task(number):
result = 0
for i in range(number):
result += 1
print(result)
pool = ThreadPoolExecutor(10)
for num in range(100):
pool.submit(task, num)
pool.shutdown(True) #等待线程池中的全部任务执行完毕
print('over')
利用线程池下载百度小说中的《西游记》
import requests
import json
from concurrent.futures import ThreadPoolExecutor
def down(title,cid):
data = {
"book_id": "4306063500",
"cid": f"4306063500|{cid}",
"need_bookinfo": 1
}
data_str = json.dumps(data)
down_url = f'https://dushu.baidu.com/api/pc/getChapterContent?data={data_str}'
down_resp = requests.get(down_url)
dics = down_resp.json()
dic = dics['data']['novel']['content']
f = open(f"西游记/{title}.txt", mode="w", encoding="utf-8")
f.write(dic)
return(f"{title}下载完成,路径为'西游记/{title}'.txt")
url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"}'
resp = requests.get(url)
data = resp.json()
data = data['data']['novel']['items']
pool = ThreadPoolExecutor(10)
podata_list = []
for i in data:
title = i['title']
cid = i['cid']
podata = pool.submit(down, title, cid)
podata_list.append(podata)
pool.shutdown(True) #等待线程池中的任务执行完毕
print('over')
for dat in podata_list:
print(dat.result()) #获取每个线程的返回值
一个进程可以有多个线程,线程之间共享一个进程资源,进程与进程之间是相互隔离的,资源并不共享
p.start() 用于启动一个新线程,使其进入就绪状态,等待系统调度执行。
p.join() 使主线程等待目标子线程t执行完毕后再继续执行。
p.daemon 用于设置线程的守护属性(必须放在start之前),决定线程是否随主线程退出而强制终止。 例:p.daemon = False (默认值,不强制终止)
p.name 为子线程设置名字(放在start之前)。 例:p.name = 'p1'
import time
import multiprocessing
def task(number):
print("当前进程名称:",multiprocessing.current_process().name)
result = 0
for i in range(number):
result += 1
print(result)
if __name__ == '__main__':
time1 = time.time()
#创建进程
numbers = [100000000,200000000]
process = [] #记录进程
for num in numbers:
p = multiprocessing.Process(target=task, args=(num,)) #创建多进程,target为运行的进程函数,args为传参(单个参数记得加逗号)
p.name = 'p'+str(num) #进程命名
p.daemon = False #不随主流程关闭子进程
p.start() #启动进程
process.append(p)
for p in process:
p.join() #阻塞主线程,强制主线程等待所有子进程完成
time2 = time.time()
print(f'多进程方式,耗时{time2-time1}秒')
import multiprocessing
count = multiprocessing.cpu_count()
print(count)
通过自定义进程类,可直接将进程需要做的事写到run方法中
import time
import multiprocessing
class MyThread(multiprocessing.Process):
def __init__(self, num): # 添加构造函数接收参数
super().__init__()
self.num = num
def run(self):
print('执行子进程')
nsum = 0
for i in range(self.num):
nsum += 1
print(f'累加和为{nsum}')
if __name__ == '__main__':
t = MyThread(100) # 直接传递参数给构造函数
t.start()
t.join() # 建议添加join等待子进程结束
当多个进程同时访问和操作共享资源时,执行顺序的不确定性可能导致意外结果
为防止该问题的出现,可通过进程锁来避免
创建锁:lock = multiprocessing.RLock()
申请锁:lock.acquire()
释放锁:lock.release()
import multiprocessing
import time
def Add(number, result, lock):
lock.acquire() # 申请锁
for i in range(number):
result.value += 1
lock.release() # 释放锁
def Sub(number, result, lock):
lock.acquire() # 申请锁
for i in range(number):
result.value -= 1
lock.release() # 释放锁
if __name__ == '__main__':
num = 500000000
# 使用Value来共享变量
result = multiprocessing.Value('i', 0)
# 创建锁
lock = multiprocessing.RLock()
time1 = time.time()
p1 = multiprocessing.Process(target=Add, args=(num, result, lock))
p2 = multiprocessing.Process(target=Sub, args=(num, result, lock))
p1.start()
p2.start()
p1.join()
p2.join()
time2 = time.time()
print(result.value)
print(f'耗时{time2-time1}')
程序中,进程并不是越多越好的,开的进程多了可能会导致系统的性能更低,所以使用进程池是很有必要的
pool = ProcessPoolExecutor(10) 创建进程池,最多维护10个进程
pool.submit(fun, arg) 提交一个进程至进程池,若进程池中有空余进程即运行该进程,反之则等待进程池空缺
pool.shutdown(True) 等待进程池中的任务全部执行完毕
from concurrent.futures import ProcessPoolExecutor
def task(name):
print(f"Process {name} is running")
if __name__ == '__main__':
# 创建最大容量为10的进程池
pool = ProcessPoolExecutor(10)
# 提交任务到进程池
for i in range(15):
pool.submit(task, i)
# 等待所有任务完成
pool.shutdown(True)
print("All processes completed")