并发编程

并发编程

1.操作系统发展史

前提:计算机真正干活的是CPU

1.穿孔卡片阶段

​ 计算机很庞大 使用很麻烦 一次只能给一个人使用 期间很多时候计算机都不工作
​ 好处:程序员独占计算机 为所欲为
​ 坏处:计算机利用率太低 浪费资源

2.联机批处理系统

​ 提前使用磁带一次性录入多个程序员编写的程序 然后交给计算机执行
CPU工作效率有所提升 不用反复等待程序录入

3.脱机批处理系统

​ 极大地提升了CPU的利用率
​ 总结:CPU提升利用率的过程

2.多道技术

在学习并发编程的过程中 不做刻意提醒的情况下 默认一台计算机就一个CPU(只有一个干活的人)

1).单道技术

所有的程序排队执行 过程中不能重合

2).多道技术

利用空闲时间提前准备其他数据 最大化提升CPU利用率

3).多道技术详细

(1).切换

​ 计算机的CPU在两种情况下会切换(不让你用 给别人用)
​ 1.程序有IO操作
​ 输入\输出操作
​ input、time.sleep、read、write
​ 2.程序长时间占用CPU
​ 我们得雨露均沾 让多个程序都能被CPU运行一下

(2).保存状态
CPU每次切换走之前都需要保存当前操作的状态 下次切换回来基于上次的进度继续执行

开了一家饭店 只有一个服务员 但是同时来了五桌客人
	请问:如何让五桌客人都感觉到服务员在服务他们
		让服务员化身为闪电侠 只要客人有停顿 就立刻切换到其他桌 如此往复

3.进程

1.进程理论

1).进程与程序的区别

程序:一堆死代码(还没有被运行起来)
进程:正在运行的程序(被运行起来了)

2).进程的调度算法(重要)

(1).FCFS(先来先服务)
对短作业不友好
(2).短作业优先调度
对长作业不友好
(3).时间片轮转法+多级反馈队列(目前还在用)

将时间均分 然后根据进程时间长短再分多个等级
等级越靠下表示耗时越长 每次分到的时间越多 但是优先级越低

2.进程的并行与并发

1).并行

多个进程同时执行 必须要有多个CPU参与 单个CPU无法实现并行

2).并发

多个进程看上去像同时执行 单个CPU可以实现 多个CPU肯定也可以

判断下列两句话孰对孰错

我写的程序很牛逼,运行起来之后可以实现14个亿的并行量
并行量必须要有对等的CPU才可以实现

我写的程序很牛逼,运行起来之后可以实现14个亿的并发量
	合情合理 完全可以实现	以后我们的项目一般都会追求高并发

ps:目前国内可以说是最牛逼的>>>:12306

3.进程的三种状态

1).就绪态

所有的进程在被CPU执行之前都必须先进入就绪状态

2).运行态

CPU正在执行

3).阻塞态

进程运行过程中出现了IO操作 阻塞态无法直接进入运行态 需要先进入就绪态

4.同步与异步

用来表达任务的提交方式

1).同步

​ 提交完任务之后原地等待任务的返回结果,期间不做任何事

2).异步

​ 提交完任务之后不原地等待任务的返回结果,直接去做其他事,有结果自动通知

5.阻塞与非阻塞

用来表达任务的执行状态

1).阻塞

​ 阻塞态

2).非阻塞

就绪态、运行态

6.综合使用

1).同步阻塞

客户端发送请求给服务端,此时服务端处理任务时间很久,客户端则被服务端阻塞了,所以客户端会一直等待服务端的响应,此时客户端不能做其他的事情,服务端也不接受其他客户端的请求。这种通信机制比较简单粗暴,但是效率不高。
"""
eg:一个进程运行,执行内部函数的时候进程要等待返回结果,这个时候cpu发现你现在在等待,cpu就不给你用来,让你进入阻塞状态。然后因为你在等待返回结果,没有继续运行别的函数,所以你处于同步状态
"""

2).同步非阻塞

客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候虽然客户端会一直等待响应,但是服务端可以处理其他请求,过一会儿回来处理原先的。这种方式很高效,一个服务端可以处理很多请求,不会再因为任务没有处理完而堵着,所以这是非阻塞。
"""
eg:一个进程运行,在执行内部函数的时候进程要等待返回结果,这个时候CPU发现你现在在等待,但是它没有直接不给你用,在给你用的同时它也在处理别的进程的任务,这个时候就是非阻塞态。但是进程虽然用着cpu,可是因为没有收到返回值,代码一致停着没有继续往下,所以他还是在同步态。
"""

3).异步阻塞

客户揣发送请求给服务端,此时服务端处理任务时间很久,但是客户端不会等待服务器响应,它可以做其他的任务,等服务器处理完毕后再把结果响应给客户端,客户端得到回调后再处理服务端的响应。这种方式可以避免客户端一直处于等待的状态,优化了用户体验,其实就是类似于网页里发起的ajax异步请求。
"""
eg:一个进程运行,执行内部函数的时候进程要等待返回结果,这个时候cpu发现你现在在等待,cpu就不给你用了,让你进入阻塞状态。然后这个进程不会一直等待最终的结果,他会继续运行别的函数,当结果出来的时候,通知一下这个进程就好了。因为进程停下来等待结果,所以处于异步状态。
"""

4).异步非阻塞

客户端发送请求给服务端,此时服务端处理任务时间很久,这个时候的任务虽然处理时间会很久,但是客户端可以做其他的任务,因为他是异步的,可以在回调函数里处理响应;同时服务端是非阻塞的,所以服务端可以去处理其他的任务,如此,这个模式就显得非常的高效了 。
"""
eg:一个进程运行,执行内部函数的时候进程要等待返回结果,这个时候cpu发现你现在在等待,但是他没有直接不给你用,在给你用的同时他也处理别的进程的任务,这个时候就是非阻塞态。然后这个进程不会一直等待最终的结果,他会继续运行别的函数,当结果出来的时候,通知一下这个进程就好了。因为进程停下来等待结果,所以处于异步状态。
"""

7.创建进程的多种方式

1.鼠标双击软件图标

2.python代码创建进程

from multprocessing import Process
import time


def task(name):
    print('task is running', name)
    time.sleep(3)
    print('task is over', name)
    
"""
在不同的操作系统中创建进程底层原理不一样
	windows
		以导入模块的形式创建进程
	linux/mac
		以拷贝代码的形式创建进程
"""
if __name__ == '__main__':
    p1 = Process(target=task, args=('jason',))  # 位置参数
    # p1 = Process(target=task, kwargs={'name': 'jason'})  # 关键字参数 不建议
    p1.start()  # 异步 高数操作系统创建一个新的进程,并在该进程中执行task函数
    task()  # 同步
    print('主')
==================================================================
from multiprocessing import Process
import time


class MyProcess(Process):
    def __init__(self, name, age):
        super().__init__()
        self.name = name
        self.age = age
        

    def run(self):
        print('run is running', self.name, self.age)
        time.sleep(3)
        print('run is over', self.name, self.age)


if __name__ == '__main__':
    obj = MyProcess('jason', 123)
    obj.start()
    print('主')

8.进程间数据隔离

同一台计算机上的多个进程数据是严格意义上的物理隔离(默认情况下)

from multiprocessing import Process
import time

money = 1000


def task():
    global money
    money = 666
    print('子进程的task函数查看money', money)


if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()  # 创建子进程
    time.sleep(3)  # 主进程代码等待3秒
    print(money)  # 主进程代码打印money

8.进程的join方法

from multiprocessing import Process
import time


def task(name, n):
    print('%s is running' % name)
    time.sleep(n)
    print('%s is over' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('jason1', 1))
    p2 = Process(target=task, args=('jason2', 2))
    p3 = Process(target=task, args=('jason3', 3))
    # p.start()  # 异步
    '''主进程代码等待子进程代码运行结束再执行'''
    # p.join()
    # print('主')
    start_time = time.time()
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    p3.start()
    p3.join()
    # p1.join()
    # p2.join()
    # p3.join()
    print(time.time() - start_time)  # 3秒多

9.IPC机制

IPC:进程间通信
消息队列:存储数据的地方 所有人都可以存 也都可以取

from multiprocessing import Queue


q = Queue(3)  # 括号内可以指定存储数据的个数
# 往消息队列中存放数据
q.put(111)
# print(q.full())  # 判断队列是否已满
q.put(222)
q.put(333)
# print(q.full())  # 判断队列是否已满
# 从消息队列中取出数据
print(q.get())
print(q.get())
# print(q.empty())  # 判断队列是否为空
print(q.get())
# print(q.empty())  # 判断队列是否为空
# print(q.get())
print(q.get_nowait())

"""
full() empty() 在多进程中都不能使用!!!
"""
==================================================================

from multiprocessing import Process, Queue


def product(q):
    q.put('子进程p添加的数据')

def consumer(q):
    print('子进程获取队列中的数据', q.get())


if __name__ == '__main__':
    q = Queue()
    # 主进程往队列中添加数据
    q.put('我是主进程添加的数据')
    p1 = Process(target=consumer, args=(q,))
    p2 = Process(target=product, args=(q,))
    p1.start()
    p2.start()
    print('主')

10.生产者消费者模型

生产者
负责产生数据的'人'
消费者
负责处理数据的'人'

该模型除了有生产者和消费者之外还必须有消息队列(只要是能够提供数据保存服务和提取服务的理论上都可以)

11.进程对象的多种方法

1.如何查看进程号
	from multiprocessing import Process, current_process
 	current_process()
 	current_process().pid  
	import os
 	os.getpid()
  	os.getppid()
2.终止进程
	p1.terminate()
	ps:计算机操作系统都有对应的命令可以直接杀死进程
3.判断进程是否存活
	p1.is_alive()
4.start()
5.join()

12.守护进程

守护进程会随着守护的进程结束而立刻结束
	eg: A是B的守护进程 一旦B嗝屁了 A立刻嗝屁
      
from multiprocessing import Process
import time


def task(name):
    print('德邦总管:%s' % name)
    time.sleep(3)
    print('德邦总管:%s' % name)


if __name__ == '__main__':
    p1 = Process(target=task, args=('A',))
    p1.daemon = True
    p1.start()
    time.sleep(1)
    print('恕瑞玛皇帝:B嗝屁了')

13.僵尸进程与孤儿进程

僵尸进程
	进程执行完毕后并不会立刻销毁所有的数据 会有一些信息短暂保留下来
 	比如进程号、进程执行时间、进程消耗功率等给父进程查看
 	ps:所有的进程都会变成僵尸进程
孤儿进程
	子进程正常运行 父进程意外死亡 操作系统针对孤儿进程会派遣福利院管理

14.多进程数据错乱问题

模拟抢票软件

from multiprocessing import Process
import time
import json
import random


# 查票
def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))


# 买票
def buy(name):
    # 再次确认票
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 判断是否有票 有就买
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print('%s买票成功' % name)
    else:
        print('%s很倒霉 没有抢到票' % name)


def run(name):
    search(name)
    buy(name)


if __name__ == '__main__':
    for i in range(10):
        p = Process(target=run, args=('用户%s'%i, ))
        p.start()
"""
多进程操作数据很可能会造成数据错乱>>>:互斥锁
	互斥锁
		将并发变成串行 牺牲了效率但是保障了数据的安全
"""

15.多进程实现TCP服务端并发

服务端

import socket
from multiprocessing import Process


def get_sercer():
    server = socket.socket()
    server = bind(('127.0.0.1', 8080))
    server.listen(5)
    return server


def get_talk(sock):
    while Ture:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())

        
if __name__ == '__main__':
    server = get_server()
    while Ture:
        sock, addr = server.accept()
        # 开设多进程去聊天
        p = Process(target+get_talk, args=(sock,))
        p.start()

客户端

import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))

while Ture:
    client.send(b'hello baby')
    data = client.recv(1024)
    print(data)

16.互斥锁代码实操

锁:建议只加载操作数据的部分 否则整个程序的效率会极低

from multiprocessing import Process, Lock
import time
import json
import random


def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s查看票 目前剩余:%s' % (name, data.get('ticket_num')))


def buy(name):
    # 先查询票数
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 买票
    if data.get('ticket_num') > 0:
        with open(r'data.json', 'w', encoding='utf8') as f:
            data['ticket_num'] -= 1
            json.dump(data, f)
        print('%s 买票成功' % name)
    else:
        print('%s 买票失败 非常可怜没车回去了!!!' % name)


def run(name, mutex):
    search(name)
    mutex.acquire()  # 抢锁
    buy(name)
    mutex.release()  # 释放锁


if __name__ == '__main__':
    mutex = Lock()  # 产生一把锁
    for i in range(10):
        p = Process(target=run, args=('用户%s号' % i, mutex))
        p.start()

17.线程

1).线程理论

进程

​ 进程其实是资源单位,表示一块内存空间线程

线程

​ 线程才是执行单位,表示真正的代码指令

我们可以将进程比喻是车间,线程是车间里面的流水线

一个进程内部至少含有一个线程

1.一个进程内可以开设多个线程

2.同一个进程下的多个线程数据是共享的

3.创建进程和线程的区别

​ 创建进程的消耗要远远大于线程

2).创建线程的两种方式

from threading import Thread
from multiprocessing import Process
import time


def task(name):
    print(f'{name} is running')
    time.sleep(0.1)
    print(f'{name} is over')


if __name__ == '__main__':
    start_time = time.time()
    # p_list = []
    # for i in range(100):
    #     p = Process(target=task, args=('用户%s' % i,))
    #     p.start()
    #     p_list.append(p)
    # for p in p_list:
    #     p.join()
    # print(time.time() - start_time)
    t_list = []
    for i in range(100):
        t = Thread(target=task, args=('用户%s' % i,))
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(time.time() - start_time)

t = Thread(target=task, args=('jason',))
t.start()
print('主线程')
"""
创建线程无需考虑反复执行的问题
"""


class MyThread(Thread):
    def run(self):
        print('run is running')
        time.sleep(1)
        print('run is over')


obj = MyThread()
obj.start()
print('主线程')

3).线程的诸多特性

(1).join方法

(2).同进程内多个线程数据共享

(3).current_thread()

(4).active_count()

4).python多线程是否有用

需要分情况
情况1
单个CPU
多个CPU
情况2
IO密集型(代码有IO操作)
计算密集型(代码没有IO)

1.单个CPU
	IO密集型
    	多进程
          申请额外的空间 消耗跟多的资源
        多线程
          消耗资源相对少 通过多到技术
        ps:多线程有优势!!!
    计算密集型
    	多进程
          申请额外的空间 消耗跟多的资源(总耗时+申请空间+拷贝代码+切换)
        多线程
          消耗资源相对较少 通过躲到急速(总耗时+切换)
        ps:多线程有优势!!!
2.多个CPU
	IO密集型
    	多进程
          总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
        多线程
          总耗时(单个进程的耗时+IO)
        PS:多线程有优势!!!
    计算密集型
    	多进程
          总耗时(单个进程的耗时)
        多线程
          总耗时(多个进程的综合)
        ps:多进程完胜!!!

实操:

from threading import Thread
from multiprocessing import Process
import os
import time


def work():
    # 计算密集型
    res = 1
    for i in range(1, 100000):
        res *= i


if __name__ == '__main__':
    # print(os.cpu_count())  # 12  查看当前计算机CPU个数
    start_time = time.time()
    # p_list = []
    # for i in range(12):  # 一次性创建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))  # 获取总的耗时

"""
计算密集型
    多进程:5.665567398071289
    多线程:30.233906745910645
"""

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))

"""
IO密集型
    多线程:0.0149583816528320
    多进程:0.6402878761291504
"""

18.GIL全局解释器锁

1).官方文档对GIL的解释

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.

"""
1.在CPython解释器中存在全局解释器锁简称GIL
	python解释器有很多类型
		CPython JPython PyPython (常用的是CPython解释器)
2.GIL本质也是一把互斥锁 用来阻止同一个进程内多个线程同时执行(重要)
3.GIL的存在是因为CPython解释器中内存管理不是线程安全的(垃圾回收机制)
	垃圾回收机制
		引用计数、标记清除、分代回收
"""

2).验证GIL的存在

from threading import Thread

num = 100


def task():
    global num
    num -= 1


t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()
print(num)

3).GIL与普通互斥锁

既然CPython解释器中有GIL 那么我们以后写代码是不是就不需要操作锁了!!!
"""
GIL只能够确保同进程内多线程数据不会被垃圾回收机制弄乱 
	并不能确保程序里面的数据是否安全
"""
import time
from threading import Thread,Lock

num = 100


def task(mutex):
    global num
    mutex.acquire()
    count = num
    time.sleep(0.1)
    num = count - 1
    mutex.release()


mutex = Lock()
t_list = []
for i in range(100):
    t = Thread(target=task,args=(mutex,))
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()
print(num)

19.死锁现象

acquire()
release()

from threading import Thread,Lock
import time

mutexA = Lock()  # 产生一把锁
mutexB = Lock()  # 产生一把锁


class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')

    def func2(self):
        mutexB.acquire()
        print(f'{self.name}抢到了B锁')
        time.sleep(1)
        mutexA.acquire()
        print(f'{self.name}抢到了A锁')
        mutexA.release()
        print(f'{self.name}释放了A锁')
        mutexB.release()
        print(f'{self.name}释放了B锁')

for i in range(10):
    obj = MyThread()
    obj.start()

20.信号量

在python并发编程中信号量相当于多把互斥锁(公共厕所)

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


sp = Semaphore(5)  # 一次性产生五把锁


class MyThread(Thread):
    def run(self):
        sp.acquire()
        print(self.name)
        time.sleep(random.randint(1, 3))
        sp.release()


for i in range(20):
    t = MyThread()
    t.start()

21.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()

22.进程池与线程池

1).进程池与线程池理论

进程和线程能否无限创建:不可以

因为硬件的发展赶不上软件,有物理极限,如果我们在编写代码的过程中无限制的创建进程或者线程可能会导致计算机崩溃

池:
降低程序的执行效率,但是保证了计算机硬件的安全

进程池:
提前创建好固定数量的进程供后续程序的调用,超出则等待

线程池:
提前创建好固定数量的线程供后续程序的调用,超出则等待

2).实操

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os
import time
import random
from threading import current_thread

# 1.产生含有固定数量线程的线程池
# pool = ThreadPoolExecutor(10)
pool = ProcessPoolExecutor(5)


def task(n):
    print('task is running')
    # time.sleep(random.randint(1, 3))
    # print('task is over', n, current_thread().name)
    # print('task is over', os.getpid())
    return '我是task函数的返回值'


def func(*args, **kwargs):
    print('from func')

if __name__ == '__main__':
    # 2.将任务提交给线程池即可
    for i in range(20):
        # res = pool.submit(task, 123)  # 朝线程池提交任务
        # print(res.result())  # 不能直接获取
        # pool.submit(task, 123).add_done_callback(func)

23.协程

1).协程简介

"""
进程:资源单位
线程:执行单位
协程:单线程下实现并发(效率极高)
	在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作
	实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的
	(该技术完全是程序员自己弄出来的 名字也是程序员自己起的)
		核心:自己写代码完成切换+保存状态
"""
import time
from gevent import monkey;

monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def func1():
    print('func1 running')
    time.sleep(3)
    print('func1 over')


def func2():
    print('func2 running')
    time.sleep(5)
    print('func2 over')


if __name__ == '__main__':
    start_time = time.time()
    # func1()
    # func2()
    s1 = spawn(func1)  # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
    s2 = spawn(func2)
    s1.join()
    s2.join()
    print(time.time() - start_time)  # 8.01237154006958   协程 5.015487432479858

2).协程实现并发

import socket
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def communication(sock):
    while True:
        data = sock.recv(1024)
        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)

s1 = spawn(get_server)
s1.join()

如何不断的提升程序的运行效率
	多进程下开多线程 多线程下开协程
posted @ 2022-11-18 17:51  dear丹  阅读(31)  评论(0编辑  收藏  举报