day04.21并发编程02

多线程优势的代码验证

单个CPU:
多个IO密集型任务>>>:
多进程:浪费资源,无法利用多个CPU
多线程:节省资源,切换+保存状态
多个计算密集型任务>>>:
多进程:耗时更长,创建进程的消耗+切换消耗
多线程:耗时较短,切换消耗

多个CPU:
多个IO密集型任务>>>:
多进程:浪费资源,多个CPU无用武之地
多线程:节省资源,切换+保存状态
多个计算密集型任务>>>:
多进程:利用多核,速度更快
多线程:速度较慢

复制代码
# 计算密集型
from threading import Thread
from multiprocessing import Process
import os
import time


def work():
    res = 1
    for i in range(1, 10000):
        res *= i


if __name__ == '__main__':
    print(os.cpu_count())  # 12  查看当前计算机CPU个数
    start_time = time.time()
    p_list = []
    for i in range(12):
        p = Process(target=work)
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    t_list = []
    for i in range(12):
        t = Thread(target=work)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print('总耗时:%s' % (time.time() - start_time))
复制代码
复制代码
# I/O密集型
from threading import Thread
from multiprocessing import Process
import os
import time


def work():
    time.sleep(2)  # 模拟纯IO操作


if __name__ == '__main__':
    start_time = time.time()
    # t_list = []
    # for i in range(100):
    #     t = Thread(target=work)
    #     t.start()
    # for t in t_list:
    #     t.join()
    p_list = []
    for i in range(100):
        p = Process(target=work)
        p.start()
    for p in p_list:
        p.join()
    print('总耗时:%s' % (time.time() - start_time))
复制代码

 

死锁现象

产生死锁的根本原因是两个或者两个以上线程在执行过程中,因争抢资源而产生相互等待的一种现象。在申请锁的时候发生了交叉闭环申请。

死锁产生的条件:

1.互斥。共享资源同时只能被一个线程访问。

2.占有且等待。线程T1在取得共享资源A的时候,请求等待资源B的时候并不释放资源A。

3.不可抢占。其他线程不能强行抢占线程的资源。

4.循环等待条件。线程T1在持有资源A1,同时在请求等待获取资源B,线程T2在持有资源B,然后在请求等待线程T1的持有资源,形成了交叉闭环申请。

复制代码
from threading import Thread, Lock
import time


mutexA = Lock()
mutexB = Lock()


class MyThread(Thread):
    def run(self):
        self.f1()
        self.f2()
    def f1(self):
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        mutexB.release()
        mutexA.release()
    def f2(self):
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        time.sleep(2)
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexA.release()
        mutexB.release()

for i in range(20):
    t = MyThread()
    t.start()
复制代码

 

信号量(了解)

信号量在不同的知识体系中,展示出来的功能是不一样的。

在并发编程中信号量意思是多把互斥锁。在django框架中信号量意思是达到某个条件自动触发特定功能。

信号量就相当于多个互斥锁的功能。

eg:

以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
复制代码
from threading import Thread, Semaphore
import time
import random

sp = Semaphore(5)  # 创建一个相当于五个互斥锁的功能


def task(name):
    sp.acquire()  # 抢锁
    print('%s正在蹲坑' % name)
    time.sleep(random.randint(1, 5))
    sp.release()  # 放锁


for i in range(1, 31):
    t = Thread(target=task, args=('伞兵%s号' % i, ))
    t.start()
复制代码

 

 

event事件(了解)

同进程的一样。线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。

即:子线程的运行可以由其他子线程决定。

复制代码
from threading import Thread, Event
import time

event = Event()  # 类似于造了一个红绿灯


def light():
    print('红灯亮着的 所有人都不能动')
    time.sleep(3)
    print('绿灯亮了 油门踩到底 给我冲!!!')
    event.set()


def car(name):
    print('%s正在等红灯' % name)
    event.wait()
    print('%s加油门 飙车了' % name)


t = Thread(target=light)
t.start()
for i in range(20):
    t = Thread(target=car, args=('熊猫PRO%s' % i,))
    t.start()
复制代码

 

进程池与线程池

进程池与线程池本质上就是一个存储进程或线程的列表。如果是I/O密集型任务,使用线程池,如果是计算密集型任务,则使用进程池。

在很多情况下需要控制进程或者线程的数量在一个合理的范围,这样可以在保证计算机硬件安全的情况下提升程序的运行效率。例如CPU程序中,一个客户端对应一个线程,虽然线程开销小,但是不能无限开,否则会耗尽系统资源,所以要控制线程数量。进程池/线程池不仅帮我们控制进程/线程的数量,还可以帮我们完成进程/线程的创建,销毁,以及任务的分配。

进程池和线程池其实降低了程序的运行效率,但是保证了硬件的安全!!!

复制代码
from concurrent.futures import ThreadPoolExecutor
from  threading import active_count,current_thread
import os,time
# 创建线程池 指定最大线程数为5  如果不指定 默认为CPU核心数 * 5
pool = ThreadPoolExecutor(5)# 不会立即开启子线程,上面的代码执行之后就会立刻创建五个等待工作的线程

print(active_count())

def task():
    print("%s running.." % current_thread().name)
    time.sleep(1)

#提交任务到线程池
for i in range(10):
    pool.submit(task)
复制代码
复制代码
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from  threading import active_count,current_thread
import os,time
# 创建进程池 最大进程数为5 默认为cpu个数*5
pool = ProcessPoolExecutor(5)# 不会立即开启子进程

# time.sleep(10)

def task():
    print("%s running.." % os.getpid())
    time.sleep(1)

if __name__ == '__main__':
    # #提交任务到进程池
    for i in range(10):
        pool.submit(task) # 第一次提交任务时会创建进程  ,后续再提交任务,直接交给以及存在的进程来完成,如果没有空闲进程就等待
复制代码

协程

进程:资源单位

线程:执行单位

协程:单线程下实现并发

单线程下,不可避免会出现IO操作,但是如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到IO阻塞就切换去执行另一个任务,这样就保证了该线程能够最大限度的处于就绪态,也就是随时可以被CPU执行的状态,相当于我们在用户程序级别将自己的IO操作最大程度的隐藏起来,从而可以迷惑操作系统:该线程好像一直在计算,IO较少,从而更多的将CPU执行权限分配给我们的线程。

协程就是单线程下的并发,又称微线程。协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

对比操作系统控制线程的切换,用户在单线程内控制协程的切换:

  优点:1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因此更加轻量级

     2.单线程内就可以实现并发的效果,最大限度利用CPU

  缺点:1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个线程,每个进程内开启多个线程,每个线程内开启协程

     2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

协程特点:

1.必须在只有一个单线程里实现并发

2.修改共享数据不需要加锁

3.用户程序里自己保存多个控制流的上下线

4.附加:一个协程遇到IO操作自动切换到其他协程

复制代码
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作
from gevent import spawn
import time


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)


def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)


start_time = time.time()
g1 = spawn(play, 'jason')
g2 = spawn(eat, 'jason')
g1.join()  # 等待检测任务执行完毕
g2.join()  # 等待检测任务执行完毕
print('总耗时:', time.time() - start_time)  # 正常串行肯定是8s+
# 5.00609827041626  代码控制切换 
复制代码

 

基于协程实现的TCP服务端的并发

复制代码
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket


def communication(sock):
    while True:
        data = sock.recv(1024)  # IO操作
        print(data.decode('utf8'))
        sock.send(data.upper())


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    while True:
        sock, addr = server.accept()  # IO操作
        spawn(communication, sock)


g1 = spawn(get_server)
g1.join()
复制代码

 

posted @   *sunflower*  阅读(62)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示