并发编程总结(2)

并发编程总结(2)

一、 信号量

  • 信号量其实也是一把锁。
  • 互斥锁只能让一个线程使用,而信号量同一时间可以让多个线程使用。在实例化一个信号量时,传的参数限定了可以给几个线程同时使用
  • 需要导一个 from threading import Semaphore

实例:

from threading import Semaphore, Lock
from threading import current_thread
from threading import Thread
import time

sm = Semaphore(5)  # 
mutex = Lock()  # 


def task():
    # mutex.acquire()
    sm.acquire()
    print(f'{current_thread().name}执行任务')
    time.sleep(1)
    sm.release()
    # mutex.release()


for line in range(20):
    t = Thread(target=task)
    t.start()
    
# 结果:
Thread-1执行任务  
Thread-2执行任务
Thread-3执行任务
Thread-4执行任务
Thread-5执行任务
 此处等待1秒  # 就是每个线程里的那个 time.sleep(1)
Thread-6执行任务
Thread-9执行任务
Thread-7执行任务
Thread-8执行任务
Thread-10执行任务
 此处等待1秒
Thread-11执行任务
Thread-14执行任务
Thread-12执行任务
Thread-13执行任务
Thread-15执行任务
 此处等待1秒
Thread-16执行任务
Thread-18执行任务
Thread-17执行任务
Thread-19执行任务
Thread-20执行任务
 此处等待1秒

二、 Event

1. 什么是Event

  • 它是threading 中的一个方法。用来控制线程的阻塞和取消阻塞。

2. Event 有什么用

  • 用来控制线程的执行,由一些线程来控制另一些线程的执行。

3. Event 的方法

  • wait 当一个线程中存在这样一个代码时,遇到它就会进入永久阻塞态。

  • set当一个线程中存在这样的代码时,遇到它会把所有的wait打开。这样原来的线程进入就绪态。

实例:

def light():
    print('红灯亮...')
    time.sleep(5)
    # 应该开始发送信号,告诉其他线程准备执行
    e.set()  # 将car中的False ---> True
    print('绿灯亮...')

def car(name):
    print('正在等红灯....')
    # 让所有汽车任务进入阻塞态
    e.wait()  # False
    print(f'{name}正在加速漂移....')

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

for line in range(10):
    t = Thread(target=car, args=(f'童子军jason{line}号', ))
    t.start()

三、 线程池与进程池

1. 什么是线程池与进程池

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

2. 进程池与线程池有什么用

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

3. 使用方法

  1. 导库:from concurrent.futures import ProcessPoolExecutor

  2. 实例化一个进程池/线程池对象: pool = ThreadPoolExecutor(n) , n可填可不填,填了表示开启n个进程/线程 , 不填表示 默认以CPU的个数限制进程数,默认以CPU个数 * 5 限制线程数

  3. submit传的参数为一个函数的地址(即函数名),作用是异步提交任务。

  4. submit(任务函数地址).add_done_callback(回调函数的地址)。当任务函数有返回值时,通过回调函数接收。回调函数的形参如resres还不是任务函数的返回值。通过res.result()得到任务函数的返回值。
  5. shutdown它会让所有线程池任务结束后,才往下执行代码,类似进程/线程中join的作用

实例:

# 任务函数没有返回值时*************************
def task():
    print('线程任务开始了...')
    time.sleep(3)
    print('线程任务结束了...')

for line in range(5):
    pool.submit(task)
    
pool.shutdown()

print('hello')   
# 任务函数有返回值时**********************************

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

# 回调函数
def call_back(res):
    print(type(res))
    # 注意: 赋值操作不要与接收的res同名
    res2 = res.result()
    print(res2)

for line in range(5):
    pool.submit(task, 1).add_done_callback(call_back)

pool.shutdown()

print('hello')    

四、协程(只使用一个线程)

1. 进程/线程/协程的区别

  • 进程:资源单位
  • 线程:执行单位
  • 协程:在单线程下实现并发
  • 注意:协程不是操作系统中的一个概念,它是人为产生的一个名字,为了让单线程实现并发。

2. 协程的作用

  • 多道技术的核心是:切换 + 保存状态
    • 一个内存中同时放入多道任务
    • 多道任务在CPU上轮流执行,遇到IO或者占用CPU时间过长就切换
    • 切换时会保存作业进度
  • 协程

    • 通过手动模拟操作系统 “多道技术” 实现 切换 + 保存状态

    • 让线程当做CPU , 多个任务 当做多进程/线程。

    • 就是让一个线程在多个任务之间来回切换 + 保存状态
    • 对于IO密集型中,遇到IO就切换 + 保存状态
    • 对于计算密集型中,来回切换 + 保存状态。对于计算密集型,使用协程效率会更低。

  • 优点:

    在IO密集型的情况下,会提高效率

  • 缺点:

    在计算密集型的情况下,因为来回切换,效率更低

3. 如何实现协程

(1)计算密集型情况

  • yield来保存状态
  • 用并发来实现切换

实例:

import time

def func1():
    while True:
        10000000+1
        yield

def func2():
    
    g = func1() # 启动生成器 ,g 为生成器对象
    for i in range(10000000):
        time.sleep(10)  # 模拟IO,yield并不会捕捉到并自动切换
        i+1
        next(g)

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

(2)IO密集型情况

  • 要导入一个第三方库from gevent import monkey,spawn , joinall

  • gevent是一个第三方库,可以帮我们监听IO操作并切换

  • 使用gevent的目的是为了实现单线程下,遇到IO, 保存状态 + 切换

  • monkey.patch_all() 可以监听程序下所有IO操作
  • spawn 和 joinall 可以实现 保存状态 + 切换

实例:

from gevent import monkey
monkey.patch_all()  # 可以监听该程序下所有的IO操作
import time
from gevent import spawn, joinall  # 用于做切换 + 保存状态

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

def func2():
    print('2')
    time.sleep(3)

def func3():
    print('3')
    time.sleep(5)
    
start_time = time.time()

s1 = spawn(func1)  # 传任务
s2 = spawn(func2)
s3 = spawn(func3)

# s2.join()  # 发送信号,相当于等待自己 (在单线程的情况下)
# s1.join()     
# s3.join()

# 必须传序列类型,如列表和元组
joinall([s1, s2, s3])

end_time = time.time()

print(end_time - start_time)

五、单线程下实现服务端并发

实例:

# 客户端*************************************
import socket
import time
from threading import Thread, current_thread


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


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

    
# 服务端*********************************

from gevent import monkey
monkey.patch_all()  # 检测IO

import socket
import time
from threading import Thread
from gevent import spawn

server = socket.socket()

server.bind(
    ('127.0.0.1', 9527)
)

server.listen(5)
print('启动服务端...')


# 线程任务,执行接收客户端消息与发送消息给客户端
def working(conn):
    while True:
        try:
            data = conn.recv(1024).decode('utf-8')
            if len(data) == 0:
                break
            print(data)
            # time.sleep(1)
            send_data = data.upper().encode('utf-8')
            conn.send(send_data)

        except Exception as e:
            print(e)
            break

    conn.close()


def server2():
    while True:
        conn, addr = server.accept()
        # print(addr)
        # t = Thread(target=working, args=(conn,))
        # t.start()
        spawn(working, conn)


if __name__ == '__main__':
    s1 = spawn(server2)
    s1.join()


六、 并发编程中遇到的模块

1. 进程中需要导入的模块和方法

from multiprocessing import Process , Lock , RLock , Queue , Semaphore , Event


# 分别用来:创建进程,创建互斥锁,创建递归锁,创建队列,创建信号量 ,创建事件

2. 线程中需要导入的模块和方法

from threading import Thread , current_thread , Lock , RLock , queue , Semaphpre , Event


# 分别用来:创建线程,返回当前线程的信息 , 创建互斥锁,创建递归锁,创建队列,创建信号量 ,创建事件

3. 进程池和线程池中需要导入的模块和方法

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


# 分别用来: 创建进程池 , 创建线程池

4. 协程中需要导入的模块和方法

from gevent import monkey , spawn , joinall


# 分别用来 监听线程中的所有IO操作 , 调用定义的任务 , 让主线程等待所有调用的任务执行结束再结束

六、五种I/O模型

1. 阻塞I/O模型

2. 非阻塞I/O模型

3. 多路复用I/O模型

4. 信号驱动I/O模型

5. 异步I/O模型

猜你喜欢

转载自www.cnblogs.com/Mcoming/p/11734409.html