python之io密集型、计算密集型、Event事件、线程Queue、定时器Timer、线程池及进程池

1、io密集型和计算密集型

# 计算密集型:

from multiprocessing import Process
from threading import Thread
import time

def task():
    count = 0
    for i in range(100000000):
        count += i

if __name__ == '__main__':
    start_time = time.time()
    list = []
    for i in range(10):
        t = Thread(target=task)  # 开线程耗费时间: 47.8338840007782
        t.start()
        t = Process(target=task)  # 开进程耗费时间: 13.326022386550903
        t.start()
        list.append(t)
    for t in list:
        t.join()
    print(time.time() - start_time)

# io密集型:

from multiprocessing import Process
from threading import Thread
import time

def task():
    time.sleep(2)

if __name__ == '__main__':
    start_time = time.time()
    list = []
    for i in range(1000):
        t = Thread(target=task)  # 开线程耗费时间: 2.106079339981079
        t.start()
        t = Process(target=task)  # 开进程耗费时间: 37.65490651130676
        t.start()
        list.append(t)
    for t in list:
        t.join()
    print(time.time() - start_time)

结论:
以下只针对于cpython解释器
在单核情况下:
开多线程还是开多进程?不管干什么都是开线程

在多核情况下:
如果是计算密集型,需要开进程,能被多个cpu调度执行
如果是io密集型,需要开线程,cpu遇到io会切换到其他线程执行

2、Event事件

Event事件: 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
比如一个线程等待另一个线程执行结束再继续执行

from threading import Thread, Event
import time

event = Event()

def light():
    print('现在是红灯')
    time.sleep(10)
    print('绿灯亮了')
    event.set()  # 告诉等待红灯的人可以走了


def car(n):
    print('汽车{}正在等待'.format(n))
    event.wait()  # 只要信号没来,就卡在这
    print('汽车{}冲了出去...'.format(n))

if __name__ == '__main__':

    t = Thread(target=light)
    t.start()

    for i in range(1, 11):
        t = Thread(target=car, args=(i,))
        t.start()
2.1、Event小案例
起两个线程,第一个线程读文件的前半部分,读完发一个信号,另一个线程读后半部分,并打印

from threading import Thread, Event
import os

event = Event()

file_size = os.path.getsize('test.txt')

def read_first():
    with open(r'test.txt', mode='rt', encoding='utf-8') as fp:
        data = fp.read(file_size // 2)
        print(data)
    print('前半部分内容读取完毕,发送信号')
    event.set()

def read_last():
    event.wait()  # 等着发信号
    with open(r'test.txt', mode='rt', encoding='utf-8') as fp:
        fp.seek(file_size // 2, 0)
        data = fp.read()
        print(data)

if __name__ == '__main__':
    t = Thread(target=read_first)
    t.start()
    t1 = Thread(target=read_last)
    t1.start()

test.txt文件内容:
0123456789


运行结果:
01234
前半部分内容读取完毕,发送信号
56789

3、线程Queue

只要你用并发,就会有锁的问题,但是不能一直去自己加锁
那么我们就用QUEUE,这样还解决了自动加锁的问题
由Queue延伸出的一个点也非常重要的概念。以后写程序也会用到
这个思想。就是生产者与消费者问题

from queue import Queue, PriorityQueue, LifoQueue

# Queue: 队列,先进先出
q = Queue(3)
q.put('allen')
q.put_nowait(18)
q.put('male', timeout=1)
print(q.get())  # allen
print(q.get())  # 18
print(q.get_nowait())  # male
print(q.full())  # False
print(q.empty())  # True

# PriorityQueue: 优先级队列,谁的数字小谁先出(数字越小,级别越高,支持负数)
q = PriorityQueue(3)
q.put((100, 'allen'))
q.put_nowait((200, 18))
q.put((-300, 'male'), timeout=1)
print(q.get())  # (-300, 'male')
print(q.get())  # (100, 'allen')
print(q.get_nowait())  # (200, 18)
print(q.full())  # False
print(q.empty())  # True

# LifoQueue: 栈,后进先出
q = LifoQueue(3)
q.put('allen')
q.put_nowait(18)
q.put('male', timeout=1)
print(q.get())  # male
print(q.get())  # 18
print(q.get_nowait())  # allen
print(q.full())  # False
print(q.empty())  # True

注意:
进程queue和线程queue不是同一个,线程间通信,因为共享变量会出现数据不安全问题,用线程queue通信,不需要加锁,内部自带,queue是线程安全的

4、定时器Timer

# 多长时间之后执行一个任务
from threading import Timer

def task(name):
    print('任务{}开始执行了'.format(name))

if __name__ == '__main__':
    t = Timer(3, task, args=('007',))  # 本质是开了个线程,延迟3秒执行
    t.start()

5、线程池

需要注意一下
不能无限的开进程,不能无限的开线程
最常用的就是开进程池,开线程池。其中回调函数非常重要
回调函数其实可以作为一种编程思想,谁好了谁就去调用
5.1、concurrent.futures
1、concurent.future模块是用来创建并行的任务,提供了更高级别的接口,
为了异步执行调用
2、concurent.future这个模块用起来非常方便,它的接口也封装的非常简单
3、concurent.future模块既可以实现进程池,也可以实现线程池
4、模块导入进程池和线程池
from  concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
还可以导入一个Executor,但是你别这样导,这个类是一个抽象类
抽象类的目的是规范他的子类必须有某种方法(并且抽象类的方法必须实现),但是抽象类不能被实例化
5、p = ProcessPoolExecutor(max_works)对于进程池如果不写max_works:默认的是cpu的数目,默认是4个
   p = ThreadPoolExecutor(max_works)对于线程池如果不写max_works:默认的是cpu的数目*5
6、如果是进程池,得到的结果如果是一个对象。我们得用一个.get()方法得到结果
   但是现在用了concurent.future模块,我们可以用obj.result方法
   p.submit(task,i)  # 相当于apply_async异步方法
   p.shutdown()  # 默认有个参数wite=True (相当于close和join)
5.2、开线程池的两种方式
5.2.1、方式一
5.2.1.1、同步方式
from concurrent.futures import ThreadPoolExecutor
import time, random

pool = ThreadPoolExecutor(5)

def task(n):
    print('任务{}开始了'.format(n))
    time.sleep(random.randint(1, 3))
    print('任务{}结束了'.format(n))
    return '任务{}'.format(n)

if __name__ == '__main__':
    for i in range(20):
        obj = pool.submit(task, i).result()
        print(obj)
    pool.shutdown()
5.2.1.2、异步方式
from concurrent.futures import ThreadPoolExecutor
import time, random

pool = ThreadPoolExecutor(5)

def task(n):
    print('任务{}开始了'.format(n))
    time.sleep(random.randint(1, 3))
    print('任务{}结束了'.format(n))
    return '任务{}'.format(n)

if __name__ == '__main__':
    list = []
    for i in range(20):
        res = pool.submit(task, i)
        list.append(res)
    for x in list:
        print(x.result())
    pool.shutdown()
5.2.2、方式二(推荐写法)
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time, random

pool = ThreadPoolExecutor(5)

def task(name):
    print('任务{}开始了'.format(name))
    time.sleep(random.randint(1, 3))
    print('任务{}结束了'.format(name))
    return '任务{}'.format(name)

def call_back(f):
    print(f.result())

if __name__ == '__main__':
    for i in range(20):
        pool.submit(task, i).add_done_callback(call_back)
5.2.3、利用线程池获取网址页面
from concurrent.futures import ThreadPoolExecutor
import requests

pool = ThreadPoolExecutor(2)

def get_page(url):
    res = requests.get(url)  # 向这个地址发送请求
    name = url.rsplit('/')[-1] + '.html'
    print(name)
    # res.content拿到页面的二进制
    return {'name': name, 'data': res.content}

def call_back(f):
    dic = f.result()
    with open(r'{}'.format(dic.get('name')), mode='wb') as fp:
        fp.write(dic.get('data'))

if __name__ == '__main__':
    url_lists = ['https://www.baidu.com', 'https://www.sina.com.cn', 'https://www.qq.com']
    for url in url_lists:
        pool.submit(get_page, url).add_done_callback(call_back)

6、进程池

# 只是在导入ProcessPoolExecutor有区别,别的都一样

from concurrent.futures import ProcessPoolExecutor
import time, random

pool = ProcessPoolExecutor(5)

def task(name):
    print('任务{}开始了'.format(name))
    time.sleep(random.randint(1, 3))
    print('任务{}结束了'.format(name))
    return '任务{}'.format(name)

def call_back(f):
    print(f.result())

if __name__ == '__main__':
    for i in range(20):
        pool.submit(task, i).add_done_callback(call_back)

7、线程池和进程池的shutdown

# 主线程等待所有任务执行完成
from concurrent.futures import ThreadPoolExecutor
import time

pool = ThreadPoolExecutor(2)

def task(name):
    print('任务{}开始执行'.format(name))
    time.sleep(1)
    print('任务{}执行结束'.format(name))

if __name__ == '__main__':

    for i in range(10):
        pool.submit(task, i)
    # 放到for外面
    pool.shutdown()  # 等待所有任务完成,并且把池关闭
    pool.submit(task, 'aaa')  # 池关闭后任务无法提交,报错
    print('主')  # 10个线程执行完毕后执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值