第37天并发编程之线程篇 (3)

多线程是串行还是并发还是并行
多线程其实也是并发的,串行指的是task1完全执行完毕之后才去执行task2,如下的代码解析。这也是CPython的一大诟病之一,虽然说在I/O密集型的操作中并不会太多的去影响性能,但是相对于多核多线程并发的来说,很显然CPython做的还不是很好。(以目前我的水品来看,觉得如果一个线程内可以有多个python解释器就好了.......哈哈哈)
from threading import Thread, current_thread

def task1(): # 1. 打印当前信息,遇到sleep之后,cpu会执行其他的操作,此时释放GIL锁 # 3. 当释放了GIL锁之后,task2会立马抢到GIL锁,然后cpu执行 print('is running ....', current_thread().name) time.sleep(1) # 6. 打印end信息之后,子线程执行完毕,释放GIL锁 print('ending1....') def task2(): # 4. 抢到GIL锁之后,打印当前信息,遇到sleep之后,cpu去执行其他的操作,此时会释放GIL锁 # 5. 此时task1睡眠完成之后,会立马去抢GIL锁,然后打印end信息 print('is running ....', current_thread().name) time.sleep(1) print('ending2....') t1 = Thread(target=task1) t2 = Thread(target=task2) # 此时会去执行task1函数 # 1. 首先我要去抢GIL锁,当抢到锁之后进入task1函数 t1.start() t2.start()

什么时候开多线程和多进程
需要有四个任务去处理
方案一:开启四个进程
方案二:开启四个线程

单核: 无论是哪种程序都应该使用方案二,因为单核情况下无论是进程还是线程都需要不停的切换执行,但是在线程的情况下可以减少开启进程的开销以及节省内存空间的使用。 多核: 1. I/O密集型,再多的核也解决不了I/O等待的问题,应该选择方案二 2. 计算密集型,多核意味着并行计算,应该选择方案一

计算密集型性能测试
from multiprocessing import Process
from threading import Thread
import time

def task1(): x = 1 for i in range(60000000): x += i def task2(): x = 1 for i in range(60000000): x += i if __name__ == '__main__': p1 = Process(target=task1) p2 = Process(target=task2) # p1 = Thread(target=task1) # p2 = Thread(target=task2) start = time.time() p1.start() p2.start() p1.join() p2.join() print(time.time() - start) # 结论 # 对于计算密集型的程序我们应该使用多进程来代替多线程。因为python中的多线程并不能利用多核实现真正的并行 # 使用多进程的结果 # 8.21526575088501 # 使用多线程的结果 # 12.482805252075195

I/O密集型性能测试
from threading import Thread
import time

def task1(): time.sleep(3) def task2(): time.sleep(3) if __name__ == '__main__': p1 = Process(target=task1) p2 = Process(target=task2) # p1 = Thread(target=task1) # p2 = Thread(target=task2) start = time.time() p1.start() p2.start() p1.join() p2.join() print(time.time() - start) # 结论 # 对于I/O密集型的程序我们应该用多线程去代替多进程 # 使用多进程的结果 # 3.2341346740722656 # 使用多线程的结果 # 3.002285242080688

三. 进程池与线程池

进程池和线程池

池就是一个容器,进程池就是用来装进程的容器,那么线程池就是用来装线程池的容器,为什么我们需要进程池和线程池呢?因为在大部分的情况下由于计算机硬件条件的限制我们并不能无线的开启进程或者线程,尽管线程的开销很小,因此我们需要用一个容器来限制能够开启的最大进程数和线程数,从而保证我们的计算机可以正常的提供服务。

进程池的简单使用

from concurrent.futures import ProcessPoolExecutor
import os
import random
import time

def task1(x):
print('%s is running..' % os.getpid())
time.sleep(random.randint(1,3))

if name == 'main':
# 创建一个进程池,进程池的大小可以通过参数进行传递, 如果不指定,默认是cpu的核数
process_pool = ProcessPoolExecutor(4)
# 当执行完submit之后,就会额外的创建出4个进程,用来执行任务
# 函数task1的参数直接在submit中输入就可以传参
process_pool.submit(task1, 1)
time.sleep(30)

在cmd中执行输入命令 【tasklist |findstr pyt】 会发现出现了5个python进程,其中有一个是主线程,另外四个就是进程池内的进程数。

线程池的简单使用

线程池的使用和进程池是一样的,只是导入的名称不一样。

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor from threading import current_thread import os import random import time def task1(x): print('%s is running..' % x) time.sleep(random.randint(1,3)) if __name__ == '__main__': # 创建一个线程池,线程池的大小可以通过参数进行传递, 如果不指定,默认是cpu的核数 * 5 process_pool = ThreadPoolExecutor(4) for i in range(20): # 当第一次执行submit之后,就会额外的创建出4个线程等待着执行任务 # 因此当我们执行了代码之后就会看到一下打印出了四行内容,之后就是执行完一个任务再进行下一个任务,最多的线程数是5个 # 其中有一个线程是主线程 process_pool.submit(task1, i) # time.sleep(2)

四.同步vs异步 阻塞vs非阻塞

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyfxjj.html