Python多线程多进程
线程是计算机CPU调度的对最小单元(真正在工作),进程是计算机资源分配的最小单元(进程为线程提供资源)。
一个进程可以有很多线程,同一个进程中的线程可以共享此进程中的资源。
由于GIL锁的存在,控制一个进程中同一时刻只有一个线程可以被CPU调度。
GIL锁
为了解决多线程共享内存的数据安全问题,引入了GIL锁,全称为Global Interpreter Lock,也就是全局解释器锁。
GIL规定,在一个进程中每次只能有一个线程在运行。这个GIL锁相当于是线程运行的资格证,某个线程想要运行,首先要获得GIL锁,然后遇到IO或者超时的时候释放GIL锁,给其余的线程去竞争,竞争成功的线程获得GIL锁得到下一次运行的机会。
正是因为有GIL的存在,python的多线程其实是假的,所以才有人说python的多线程非常鸡肋。但是虽然每个进程有一个GIL锁,进程和进程之间还是不受影响的。
CPU密集型操作使用多进程比较合适,例如海量运算。
IO密集型操作使用多线程比较合适,例如爬虫,文件处理,批量ssh操作服务器等等。
一、多线程
1、没有使用线程
import time
def work(i):
print('{}开始任务'.format(i))
time.sleep(3)
print('{}结束任务'.format(i))
start_time = time.time()
for i in range(5):
work(i)
print('所有的任务完成')
end_time = time.time()
print(end_time - start_time)
# 0开始任务
# 0结束任务
# 1开始任务
# 1结束任务
# 2开始任务
# 2结束任务
# 3开始任务
# 3结束任务
# 4开始任务
# 4结束任务
# 所有的任务完成
# 15.042697191238403
2、使用多线程
import threading
import time
def work(i):
print(threading.current_thread())
print('{}开始任务'.format(i))
time.sleep(3)
print('{}结束任务'.format(i))
start_time = time.time()
ts = []
for i in range(5):
t = threading.Thread(target=work,args=(i,))
t.start()
ts.append(t)
for t in ts:
t.join()
print('所有的任务完成')
end_time = time.time()
print(end_time - start_time)
# <Thread(Thread-4, started 17792)>
# 0开始任务<Thread(Thread-5, started 26072)>
# 1开始任务
# <Thread(Thread-6, started 16180)>
# 2开始任务
# <Thread(Thread-7, started 2492)>
# 3开始任务
# <Thread(Thread-8, started 15588)>
# 4开始任务
# 3结束任务4结束任务
# 1结束任务
# 0结束任务2结束任务
# 所有的任务完成
# 3.0036520957946777
没有使用线程的时间是15秒,使用线程的时间是3秒,多线程的时间是执行最久的线程的时间。
3、线程池
from concurrent.futures import ThreadPoolExecutor
import threading
def work(i):
print(threading.current_thread())
print('{}开始任务'.format(i))
time.sleep(3)
print('{}结束任务'.format(i))
return i
with ThreadPoolExecutor() as pool:
result = pool.map(work,list(range(5)))
for res in result:
print(res)
# <Thread(ThreadPoolExecutor-0_0, started 21760)>
# 0开始任务
# <Thread(ThreadPoolExecutor-0_1, started 16872)>
# 1开始任务
# <Thread(ThreadPoolExecutor-0_2, started 18192)>
# 2开始任务
# <Thread(ThreadPoolExecutor-0_3, started 17596)>
# 3开始任务
# <Thread(ThreadPoolExecutor-0_4, started 1360)>
# 4开始任务
# 2结束任务0结束任务4结束任务
# 3结束任务1结束任务
# 0
# 1
# 2
# 3
# 4
4、线程的通信安全
import threading
count = 0
def fun_add():
global count
for i in range(1000000):
count += 1
def fun_sub():
global count
for i in range(1000000):
count -= 1
t1 = threading.Thread(target=fun_add)
t2 = threading.Thread(target=fun_sub)
t1.start()
t2.start()
t1.join()
t2.join()
print(count)
这段程序执行结果count不等于0,存在通信安全,所以要使用锁
5、线程锁
锁 lock= threading.RLock()
lock.acquire() 加锁
lock.release()释放锁
两个线程用同一把锁
列表append extend 索引取值 pop sort 都是线程安全的 不用加锁
Lock同步锁 不支持连续锁 但锁一次解一次的效率比递归锁高
RLock 递归锁 支持锁多次
推荐使用 RLock
import threading
count = 0
lock = threading.RLock()
def fun_add():
global count
for i in range(1000000):
lock.acquire()
count += 1
lock.release()
def fun_sub():
global count
for i in range(1000000):
lock.acquire()
count -= 1
lock.release()
t1 = threading.Thread(target=fun_add)
t2 = threading.Thread(target=fun_sub)
t1.start()
t2.start()
t1.join()
t2.join()
print(count)
# 0
二、多进程
1、没有使用多进程
import time
def cal(s,e):
sum = 0
for i in range(s,e):
sum = sum + i
return sum
start_time = time.time()
a = cal(1,100000000)
end_time = time.time()
print(end_time - start_time)
# 3.229938268661499
2、使用多进程
多进程放在 if name == 'main’中 执行
from multiprocessing import Process,Queue
import time
def cal(q,s,e):
sum = 0
for i in range(s,e):
sum = sum + i
print(sum)
q.put(sum)
if __name__ == '__main__':
start_time = time.time()
q = Queue()
p = Process(target=cal, args=(q, 0, 50000000))
p1 = Process(target=cal, args=(q, 50000000, 100000000))
p.start()
p1.start()
p.join()
p1.join()
print('主进程获取Queue数据')
print(q.get()+q.get())
print('结束测试')
end_time = time.time()
print(end_time - start_time)
# 3749999975000000
# 1249999975000000
# 主进程获取Queue数据
# 4999999950000000
# 结束测试
# 1.9793572425842285
CPU密集型的程序使用多进程要比不使用时间快,上面代码使用队列Queue进行数据通信,put将数据存放在队列中,get获取队列中的数据
3、使用进程池,用队列进行数据通信
使用进程池,队列的方法用Manager().Queue()
# 使用进程池
from multiprocessing import Manager,Pool
import time
def cal(q,s,e):
sum = 0
for i in range(s,e):
sum = sum + i
print(sum)
q.put(sum)
if __name__ == '__main__':
start_time = time.time()
q = Manager().Queue()
pool = Pool(2)
for i in range(2):
pool.apply_async(func=cal,args=(q,i*50000000,(1+i)*50000000))
pool.close()
pool.join()
print('主进程获取Queue数据')
print(q.get()+q.get())
print('结束测试')
end_time = time.time()
print(end_time - start_time)
# 1249999975000000
# 3749999975000000
# 主进程获取Queue数据
# 4999999950000000
# 结束测试
# 2.4994184970855713
4、使用Manager进行数据共享
使用Manager可以对共享的数据进行修改
from multiprocessing import Manager,Pool
import time
def cal(datas,s,e):
print(s)
sum = 0
for i in range(s,e):
sum = sum + i
datas.append(sum)
if __name__ == '__main__':
pool = Pool(2)
with Manager() as manager:
start_time = time.time()
datas = manager.list()
process_list = []
for i in range(2):
pool.apply_async(func=cal,args=(datas,i*50000000,(1+i)*50000000))
pool.close()
pool.join()
print('主进程获取datas数据')
print(datas[0]+datas[1])
print('结束测试')
end_time = time.time()
print(end_time - start_time)
print(datas)
# 1249999975000000
# 3749999975000000
# 主进程获取datas数据
# 4999999950000000
# 结束测试
# 1.6817915439605713
# [1249999975000000, 3749999975000000]