python擅长处理密集型cpu计算_Python进阶----GIL锁,验证Cpython效率(单核,多核(计算密集型,IO密集型)),线程池,进程池...

day35

一丶GIL锁

什么是GIL锁:

存在Cpython解释器,全名:全局解释器锁.(解释器级别的锁)

​   GIL是一把互斥锁,将并发运行变成串行.

​   在同一个进程下开启的多个线程,同时只能有一个线程执行,无法利用多核优势

GIL锁的作用:

​   保证同一时间内,共享数据只能被一个任务修改.保证数据的完整性和安全性

​   自动上锁和解锁,不需要人为的添加.减轻开发人员的负担

所谓诟病:单进程的多线程不能利用多核

通常有人会认为GIL锁不能利用多核处理任务,是Python语言的诟病.个人认为任何一门语言都不是完美的,python问世于20世纪末,当时的CPU也只是单核.

So,不能利用多核的原因是:同一个进程下,只允许一个线程对共享内容进行修改.不允许多线程同时处理共享数据.

(打个比方:现在你家有10升(共享数据:10)牛掰的牛奶,你免费送给大家每人1升. 现在有100个人(100个线程)知道了这件事,都上你家来取奶.肯定谁先来,先得. 现在来了10个人(10个线程),每个人都说我去了1升,还剩9升.你真的还剩9升吗? 毛线~~ ,你球都不剩了. 后面来的90个人还能拿到奶吗? 肯定拿拿不到了. So你是不是得上锁,比如你家大门就是一把锁.每次只允许进来(上锁)1个人去奶.取完之后把总的奶量 减一. 第一个取完了出了大门(解锁).剩余的99个人开始抢着进你家门,公平竞争,谁先到你家门,先进就把门锁上了此时发现奶的数量还剩9升,第二个人也只取1升....往后依次延续,当第十一个人进来时,肯定奶已经没了.就表示数据为0.不能再取奶.保证奶的数量的安全性,和完整性(这还是装奶的容器))

GIL抽象图:

黑色方框就代表GIL锁

二丶验证Cpython的并发效率

需求:

现在有4个任务(计算密集型任务 或者 IO密集型任务)需要处理.

方案:

一.开启四个进程

​   二.开启一个进程,四个线程

结果:

单核:

​      IO密集型:开启进程开销大,进程切换速度远不如线程. 采用一个进程四个线程方案

计算密集型:没有多核来并行计算,徒增创建进程时间,采用一个进程四个线程方案

多核:

IO密集型:尽管多核,开启进程也比较耗时,而且还是要不断的切换.不如线程快.采用一进四线方案

计算密集型:多核,开启进程并行计算.此时如果用四个线程来会切换回损耗时间.采用四进程方案

多核处理计算密集型:

多进程的并行执行 比 单进程的多线程 效率高

# -*-coding:utf-8-*-

# Author:Ds

### 计算密集型

#开启四个进程 , 开启四个线程

from multiprocessing import Process

from threading import Thread

import time

import os

def task1():

res=1

for i in range(1,100000000):

res+=i

def task2():

res=1

for i in range(1,100000000):

res+=i

def task3():

res=1

for i in range(1,100000000):

res+=i

def task4():

res=1

for i in range(1,100000000):

res+=i

if __name__ == '__main__':

start_time=time.time()

### 多核四进程: 处理计算密集型任务,效率高

# 开启四个进程

# p1=Process(target=task1,) # 6.557461261749268

# p2=Process(target=task2,)

# p3=Process(target=task3,)

# p4=Process(target=task4,)

### 虽然本人计算机是多核,但是由于GIL所的原因,同一时刻只允许一个线程处理任务

### 一进程四线程: 处理计算密集型任务,效率低

#开启四个线程

p1=Thread(target=task1,) # 22.048070430755615

p2=Thread(target=task2,)

p3=Thread(target=task3,)

p4=Thread(target=task4,)

p1.start()

p2.start()

p3.start()

p4.start()

p1.join()

p2.join()

p3.join()

p4.join()

print(f'主:{time.time()-start_time}')

#### 总结:

#计算密集型: 多进程的并行执行 ~比~ 单进程的多线程 效率高

多核处理IO密集型:

任务是IO密集型并且任务数量很大,用单进程下的多线程效率高.

from multiprocessing import Process

from threading import Thread

import time

import os

def task1():

res=1

time.sleep(3)

if __name__ == '__main__':

start_time=time.time()

### 并行处理 四核 开启一个进程,使用一个CPU

# 开启进程需要耗费大量时间

# l_t=[]

# for i in range(150):

# ### 多进程处理IO密集型

# p=Process(target=task1) # 耗时: 9.513447999954224

# l_t.append(p)

# p.start()

#

# for j in l_t:

# j.join()

### 并发处理

#虽然是并发处理 ,但是会在CPU之间来回切换.提高效率

l_t = []

for i in range(150):

### 多线程处理IO密集型

p = Thread(target=task1) # 耗时: 3.0212857723236084

l_t.append(p)

p.start()

for j in l_t:

j.join()

print(f'主:{time.time()-start_time}')

#### 总结:

#IO密集型: 任务是IO密集型并且任务数量很大,用单进程下的多线程效率高.

三丶GIL与互斥锁的关系

GIL vs ThreadLock:

​   GIL保护的是解释器级的数据,自动上锁,自动解锁.

Lock是保护用户自己的数据,手动上锁,手动解锁.如下图:👇

GIL锁和Lock锁

1.处理任务时,首先拿到的一定是GIL锁.

​      一个任务相当于一个进程(开辟空间包含所有资源,同时也包含Cpython解释器(意味着先拿到GIL锁)),一个进程包含一条线程(cpu执行最小单元). 任务就是自己编写的代码自己可以上Lock锁.

​      执行顺序: 操作系统调度分配---->内存中开启进程就是:空间,加载资源(Cpython解释器(拿到GIL锁),自定义py文件等)--->执行自定义程序(执行线程 ,拿到自定义Lock锁(上锁,释放))---->执行完线程释放GIL锁

​2.LOCK锁 ,一定要加在处理共享数据的地方

如果自定义的lock锁随意加,会导致程序出现各种问题. 而且不符合Lock锁规则.Lock是对共享数据的限制,操作共享数据时是串行,保证并发时多个任务(多线程,多进程)修改共享数据的安全性和完整性.

四丶进程池,线程池

​   **concurrent.futures模块 提供了高度封装的异步调用接口. 符合鸭子模型 **.

定义:

一个存在线程.进程数量的容器.

​   每天计算机的资源是有限的,不能随意的开启线程或进程.都会有一个上限, '池'就是用来存放开启这些资源最大的限度.

作用:

最大限度的合理利用计算机资源处理任务,保证计算机物理安全性(开的进程或线程长期超过上限,可能损坏物理硬件)

进程池案例:

# -*-coding:utf-8-*-

# Author:Ds

#### 进程池

# 能够利用多核 处理任务

from concurrent.futures import ProcessPoolExecutor

import time

import os

import random

def task(name):

print(name)

print(f'{os.getpid()} 准备接客')

time.sleep(random.randint(1,3))

if __name__ == '__main__':

p = ProcessPoolExecutor(max_workers=5) #默认一般起进程: 计算机核数 or 1

#### for 使用

# for i in range(23):

# submit 提交执行函数,返回一个结果对象

# p.submit(task,os.getpid()) # 给进程池放任务,传参

### map取代for +submit

# 函数名,可迭代对象,

p.map(task,range(5))

p.shutdown(wait=True) # 进程池的pool.close()+pool.join()操作

线程池案例:

from concurrent.futures import ThreadPoolExecutor

import time

import os

import random

def task(name):

print(name)

print(f'{os.getpid()} 准备接客')

time.sleep(random.randint(1,3))

if __name__ == '__main__':

t = ThreadPoolExecutor(max_workers=5) #默认一般起线程的数据不超过CPU个数*5

#### for 使用

for i in range(23):

# submit 提交执行函数,返回一个结果对象

t.submit(task,os.getpid()) # 给进程池放任务,传参

### map取代for +submit

# 函数名,可迭代对象,

t.map(task,range(5))

t.shutdown(wait=True) # 进程池的pool.close()+pool.join()操作

线程池,爬取网页:

from concurrent.futures import ThreadPoolExecutor

from urllib.request import urlopen

def get_html(name,addr):

ret=urlopen(addr)

# print(ret.read().decode('utf-8'))

return {'name':name,'content':ret.read()}

def parser_page(ret_obj):

dic=ret_obj.result()

with open(dic['name']+'.html','wb') as f:

f.write(dic['content'])

# print(ret_obj)

url_lst={

'淘宝':'https://www.taobao.com',

'京东':'https://www.jd.com/2019',

'百度':'http://www.baidu.com'

}

t=ThreadPoolExecutor(20)

for url in url_lst:

task=t.submit(get_html,url,url_lst[url])

# 回调函数

task.add_done_callback(parser_page)

### 使用with上下文管理

with ThreadPoolExecutor(20) as t:

for url in url_lst:

task=t.submit(get_html,url,url_lst[url])

# 回调函数

task.add_done_callback(parser_page)

t.map()

t.shutdown()

#### 使用多线程去执行 get html

#### 一旦get_html执行结束后 ,立即使用parser_page函数来分析获取的页面结果

# 进程池 除非是计算密集型的场景: 否则几乎不用 CPU的个数*2

# 线程池 一般情况启动 CPU的个数*5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值