1024 笔记

1. Event事件

定义

Event事件的作用: 用来控制线程

事件处理的机制:全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True,那么event.wait 方法时便不再阻塞。 

方法

  • set() : 将标志设为True,使处于阻塞状态的线程恢复运行状态。
  • wait(): 如果标志为True将立即返回,否则线程阻塞(可传时间)
  • clear(): 将标志设为False。
  • isSet(): 获取内置标志状态,返回True或False。
from threading import Event
import time
from threading import  Thread
# 调用Event类实例化一个对象
e = Event()

def light():
    print('红灯亮...')
    time.sleep(5)
    # 发送信号true,其他所有进程准备执行
    e.set()  # flag变为true ,阻塞态变为运行
    print('绿灯亮...')

def car(name):
    print('正在等红灯...')
    # 所有汽车🚕处于阻塞态
    e.wait()  # 为false是阻塞,直到为true
    print(f'{name}出发...')

# 让一个线程任务控制多个car任务
t = Thread(target=light)
t.start()

for i in range(10):
    s = Thread(target=car,args=(f'{i}号赛车',))
    s.start()
当light线程执行时,打印'红灯亮',IO操作切换线程执行car线程,打印'正在等红灯',wait时flag是false所以阻塞,遇到IO切换,执行第二个car打印'正在等红灯',.....
直到5秒结束,set让flag变为true,打印'绿灯亮',IO操作

2. 进程池与线程池

定义

进程池与线程池是用来控制当前程序允许创建(进程/线程)的数量.

当我们需要开启多个线程或进程时,难道只能一个个去开吗?
这时我们可以开辟一个进程池或线程池,可以一次就开启多个进程或线程,池的简单实现也可以用生产者与消费者模式来实现。

主线程: 相当于生产者,只管向线程池提交任务。并不关心线程池是如何执行任务的。因此,并不关心是哪一个线程执行的这个任务。

线程池: 相当于消费者,负责接收任务,并将任务分配到一个空闲的线程中去执行。

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

使用线程池可以有效地控制系统中并发线程的数量

作用

保证在硬件允许的范围内创建(进程/线程)的数量.

使用

进程池

from concurrent.futures import ProcessPoolExecutor来调用

ProcessPoolExecutor(5) # 代表只能开启5个进程
ProcessPoolExecutor() # 不写默认以cpu的个数限制进程数

线程池

from concurrent.futures import ThreadPoolExecutor

pool = ThreadPoolExecutor(5)  # 代表只能开启5个进程
pool = ThreadPoolExecutor()  # 不写默认以'cpu个数*5'限制进程数

.submit()

.submit('传函数地址') 异步提交任务,与t = Thread() t.start() 一样

from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(5)  # 同时提交5个线程任务

def task():
    print('线程任务开始...')
    time.sleep(1)
    print('线程任务结束...')

for i in range(5):
    pool.submit(task)

回调函数add_done_callback()

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

异步回调指的是:在发起一个异步任务的同时指定一个函数,在异步任务完成时会自动的调用这个函数。

pool.submit('传函数地址').add_done_callback('添加回调函数')

'''submit(task)执行task函数拿到task返回值,通过add_done_callback将返回值传入回调函数,回调函数需传形参,得到的是对象通过`对象.result()`得到线程任务返回的结果'''
from concurrent.futures import ThreadPoolExecutor
import time
pool = ThreadPoolExecutor(5)  # 同时提交5个线程任务

def task():
    print('任务开始...')
    time.sleep(1)
    print('线程任务结束...')
    return 123

# 回调函数
def call_b(res):
    # 渎职操作不要与接收的res重名
    data = res.result()  #  .result()去取出res对象中的值
    print(data)

for i in range(5):
    pool.submit(task).add_done_callback(call_b)

3. 协程

定义

线程是系统级别的,他们有操作系统调度  
协程是程序级别的,由程序根据需要自己调度

在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行

一个程序中有很多函数,称之为子程序.在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可中断回来继续执行此子程序,这个过程就称之为协程, 类似于yield操作,但yield不会检测IO操作.

--协程--
通过手动模拟操作系统的'多道技术',实现切换+保存 状态

手动实现 遇到IO切换,欺骗操作系统误以为没有IO操作
	单线程 遇到IO,   切换 + 保存状态
	单线程 计算密集, 来回切换 + 保存状态,反而效率更低
优点:
	在IO密集型的情况下,会提高效率
缺点:
	计算密集型,来回切换,反而效率较低

Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持

代码实现

验证计算密集型的情况下

串行的情况下
import time
def func1():
    for i in range(10000000):
        i + 1

def func2():
    for i in range(10000000):
        i + 1

start = time.time()
func1()
func2()
end = time.time()
print(f'用时{end-start}')   # 用时1.9524507522583008

==========================================================

切换+保存实现并发
import time
def func1():
    while True:
        10000000 + 1
        yield
def func2():
    # g 是生成器对象
    g = func1()
    for i in range(10000000):
        i + 1
        next(g)  # 每次执行next相当于切换到func1下面

start = time.time()
func2()
stop = time.time()
print(stop-start)   # 3.2243001461029053

IO密集型的情况下

如何检测到IO操作,引出gevent第三方库实现并发同步或异步编程,遇到IO阻塞会自动切换任务

pip install gevent    # 安装第三方库
from gevent import monkey	# 进行导入monkey
monkey.patch_all()  # 可以监听该程序下的所有IO操作
from gevent import spawn # 用于做切换+保存状态
from gevent import monkey
monkey.patch_all()  # 可以监听该程序下的所有IO操作
from gevent import spawn # 用于做切换+保存状态
import time

def func1():
    print('1')
    # IO操作
    time.sleep(3)

def func2():
    print('2')
    # IO操作
    time.sleep(1)

def func3():
    print('3')
    # IO操作
    time.sleep(5)

start = time.time()

# 调用spawn(任务函数)  让其单线程下实行并发
s1 = spawn(func1)
s2 = spawn(func2)
s3 = spawn(func3)

# join发送信号,相当于等待自己结束再全结束(单线程)
s1.join()
s2.join()
s3.join()

stop = time.time()

print(stop-start)   # 5.014296770095825
# 实现并发执行,func123会并发一起执行

4. TCP服务端实现协程

'''服务端'''
from gevent import monkey
monkey.patch_all() # 检测IO
import socket
from gevent import spawn

s = socket.socket()
s.bind(
    ('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')
def work(conn):
    while True:
        try:
            data = conn.recv(1024)
            print(data.decode('utf-8'))
            conn.send(data.upper())
        except Exception as e:
            print(e)
            break
    conn.close()

def server():
    while True:
        conn,addr= s.accept()
        print(f'用户{addr}已连接')
        # 监听work函数,进行并发
        spawn(work,conn)

if __name__ == '__main__':
    # 监听server函数,进行并发
    s1 = spawn(server)
    s1.join()
    
==========================================================
'''客户端'''
import socket
from threading import Thread, current_thread

def client():
    c = socket.socket()
    c.connect(
        ('127.0.0.1',8848)
    )
    print('启动客户端...')
    num = 0
    while True:
        data = f'{current_thread().name}{num}'
        c.send(data.encode('utf-8'))
        da = c.recv(1024)
        print(da.decode('utf-8'))
        num += 1

# 模拟30个用户并发去访问客户端
for i in range(30):
    t = Thread(target=client)
    t.start()

5. IO模型

Python中的io模块是用来处理各种类型的I/O操作流。主要有三种类型的I/O类型:文本I/O(Text I/O),二进制I/O(Binary I/O)和原始I/O(Raw I/O)。它们都是通用类别,每一种都有不同的后备存储。属于这些类别中的任何一个的具体对象称为文件对象,其他常用的术语为流或者类文件对象。

IO发生时涉及的对象和步骤。对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段: 

1)等待数据准备 (Waiting for the data to be ready)
2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process) 

常用四种IO模型

  • 阻塞IO(blocking IO)
  • 非阻塞IO(non-blocking IO)
  • 多路复用IO(IO multiplexing)
  • 异步IO(Asynchronous I/O)
posted @ 2019-10-24 22:08  fwzzz  阅读(343)  评论(0编辑  收藏  举报