Python学习第十五天——并发编程、进程、线程、协程

操作系统发展史:

1.手工操作——穿孔卡片

2.批处理

​ ①.联机批处理

​ ②.脱机批处理

3.多道程序设计技术

​ 遇到I/O操作时,CPU执行其他程序

4.分时系统

​ 时间片

5.通用操作系统

​ 多道批处理、分时

进程:

​ 为什么要有进程?

​ 程序:二进制文件(硬盘)

​ 进程:二进制文件(内存)+ PCB(task_struct)

​ 缺点: 1.进程只能在一个时间干一件事情,如果想同时干两件事或多件事,进程就无能为力了。

​ 2.进程在执行的过程中如果阻塞,列入等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也无法执行

​ 线程:让一个线程执行一个子任务,这样一个进程就包含了多个线程。每个线程负责一个单独的子任务。

​ 狭义定义:进程是正在运行的进程的实例

​ 广义定义:进程是一个具有一定独立功能的程序关于某一数据集合的依次运行活动。

进程是资源分配的最小单位。线程是运行(系统调度)的最小单位。

一个程序运行最少有一个进程,一个进程里最少有一个线程。

理解:

​ 与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程

进程的状态:

​ 就绪态:可以被CPU执行,但没有被执行

​ 运行态:运行中

​ 阻塞态:I/O操作,把数据加载到内存中

并发和并行:

​ 并发:时间段

​ 并行:同一时刻

进程调度算法

​ 1.先来先服务

​ 2.短作业优先

​ 3.时间片轮转算法

​ 4.多级反馈队列

同步、异步描述别人(调用对象)

阻塞、非阻塞描述自己(自己状态)

四类:同步阻塞、同步非阻塞、异步阻塞、异步非阻塞

进程类Process

调用:
from multiprocessing import Process
形参:

​ group参数未使用,值始终为None

​ target表示调用对象,即子进程要执行的任务

​ args表示调用对象的位置参数元组,args=(1,2,'egon',),位置实参

​ kwargs表示调用对象的字典,kwargs={'name':'egon','age':18},关键字实参

​ name为子进程的名称

方法:

​ p是对象不是进程。

​ p.start() #启动进程,必须调用start

​ p.run() #实际进程在执行时,执行的是run方法,但是调用run不会开进程,后面我们另一种开启进程方案提及

​ p.join() # 等待子进程执行完成

​ p.terminate() # 杀死p进程,并不是立即结束而是通知系统。

​ p.is_alive() #看进程是否活着

属性:

​ p.name #进程的名字

​ p.pid #进程号

​ p.daemin = True #主进程结束,子进程也结束。默认False ,必须在start之前调用.

如果在任务中取出进程id号,需要使用os模块

print(os.getpid()) #子进程id号(自己的)

print(os.getppid()) #父进程id号
开启进程的两种方式:
1.由Process类生成对象,开启进程

import time
from multiprocessing import Process

def f(name):
    print('hello', name)
    print('我是子进程')

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    time.sleep(1)
    print('执行主进程的内容了')

2.继承Process类,重写run方法

import os
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        print(os.getpid())
        print('%s 正在和女主播聊天' %self.name)

p1=MyProcess('wupeiqi')
p2=MyProcess('yuanhao')
p3=MyProcess('nezha')

p1.start() # start会自动调用run
p2.start()
# p2.run()
p3.start()


p1.join()
p2.join()
p3.join()

print('主线程')

进程锁Lock

调用:
from multiprocessing import Lock
使用方式:
一、手动添加
lock.acquire()
被锁进程
lock.release()
二、自动
with lock

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。

虽然可以用文件共享数据实现进程间通信,但问题是:

效率低(共享数据基于文件,而文件是硬盘上的数据)
需要自己加锁处理

进程Queue

调用:
from multiprocessing import Queue
实例化得到一个对象

queue = Queue(3)

queue.put()

block 是否阻塞 默认为True

timeout 等待时间

queue.get()

block 是否阻塞

timeout 等待时间

相当于block = False

queue.put_nowait()

queue.get_nowait()

判断队列是否为满

queue.full()

判断队列是否为空

queue.empty()

查看queue中有多少值

queue.qsize()

进程间数据共享Manager

魔法方法:类内以__ 开头 __结尾的方法,都叫魔法方法,某种情况下会触发它的执行
__ init __ :类()触发

进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的。虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此。
举例

from multiprocessing import Manager,Process,Lock
def work(d,lock):
    with lock:  # 不加锁而操作共享的数据,肯定会出现数据错乱
        d['count']-=1

if __name__ == '__main__':
    lock=Lock()
    with Manager() as m:
        dic=m.dict({'count':100})
        p_l=[]
        for i in range(100):
            p=Process(target=work,args=(dic,lock))
            p_l.append(p)
            p.start()
        for p in p_l:
            p.join()
        print(dic)

线程

#进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程。

全局解释器锁(GIL)

python因为自身的原因需要引入进程。GIL锁,一个cpu同一时刻只能运行一个线程

动态语言问题,无需编译,不可预知参数是否还会使用,导致GC机制,需要加锁。

1.pypy(没有全局解释器锁)快 但是好多模块无法使用

2.cpython

只针对cpython解释器

如果是计算密集型:需要进程

如果是I/O密集型:要开线程

线程类Thread

from threading import Thread
import time

#
# def task():
#     time.sleep(1)
#     print('我是子线程')
#
#
# if __name__ == '__main__':
#     t=Thread(target=task)
#     t.start()
#     print('我是主线程')
#
#


# 第二种方式

class MyThread(Thread):`
    def __init__(self,a):
        self.a=a
        super().__init__()
    def run(self):
        time.sleep(1)
        print('我是子线程',self.a)

if __name__ == '__main__':
    t=MyThread('aaaaa')
    t.start()
    print('我是主线程')

Thread实例对象的方法:

isAlive():返回线程是否活动的。
getName():返回线程名。
setName():设置线程名。
join(): 主进程等待子线程执行结束。

threading模块提供的一些方法:

threading.currentThread():返回当前的线程变量。

threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

属性:
t.setDaemon(True) # 如果主线程执行结束,子线程也结束(不执行了),别名守护线程。
t.name #线程的名字
t.pid #线程号
注意os.getpid()获取的是进程号。

同步锁(互斥锁)

调用方法:
from threading import Lock
使用方法:

from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

信号量Semaphore

# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据。
from threading import Thread,Semaphore
import time
import random
sm=Semaphore(5)

def task(name):
    sm.acquire()
    print('%s正在蹲坑'%name)

    time.sleep(random.randint(1,3))
    sm.release()

if __name__ == '__main__':
    for i in range(100):
        t=Thread(target=task,args=(i,))
        t.start()

Event

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

from threading import Thread,Event
import time
event=Event()
def girl(name):
    print('%s 还在谈着恋爱'%name)
    time.sleep(3)
    event.set()
    print('%s 单身了'%name)


def boy(name):
    print('%s 等着女神分手'%name)
    event.wait()
    print('女神分手了,开始追')

if __name__ == '__main__':
    t=Thread(target=girl,args=('刘亦菲',))
    t.start()
    for i in range(5):
        t=Thread(target=boy,args=('屌丝男%s号'%i,))
        t.start()

死锁问题(递归锁,可重入锁)

所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
解决方法:递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁。

死锁举例:

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

解决哲学家问题:

### 解决死锁问题  RLock:可重入,可以重复acquire,获得几次,就要释放几次
from threading import Thread, Lock,RLock
import time
import random


def eat1(lock_1, lock_2, name):
    lock_1.acquire()
    print('%s:拿到了筷子' % name)
    time.sleep(random.random())
    lock_2.acquire()
    print('%s:拿到了面条' % name)
    print('开始吃面')
    time.sleep(random.random())
    lock_2.release()
    print('%s放下了面条' % name)
    lock_1.release()
    print('%s放下了筷子' % name)


def eat2(lock_1, lock_2, name):
    lock_2.acquire()
    print('%s:拿到了面条' % name)
    time.sleep(random.random())
    lock_1.acquire()
    print('%s:拿到了筷子' % name)
    print('开始吃面')
    time.sleep(random.random())
    lock_1.release()
    print('%s放下了筷子' % name)
    lock_2.release()
    print('%s放下了面条' % name)


if __name__ == '__main__':
    lock_1 = RLock()
    lock_2 = lock_1

    # lock_1 = Lock()
    # lock_2 = lock_1
    for i in ['张三', '李四', '王五']:
        t = Thread(target=eat1, args=[lock_1, lock_2, i])
        t.start()
    for i in ['冯6', '刘7', '邱8']:
        t = Thread(target=eat2, args=[lock_1, lock_2, i])
        t.start()

线程队列

  1. 线程Queue,解决线程间数据共享的问题
  2. 线程间数据共享可以使用共享变量(可能会存在并发安全的问题)
from threading import Thread
from queue import Queue,LifoQueue,PriorityQueue # 线程queue
# Queue:先进先出
#LifoQueue:后进先出
#PriorityQueue:优先级队列


if __name__ == '__main__':
    # 先进先出
    # quque1=Queue()
    # quque1.put(1)
    # quque1.put(2)
    # print(quque1.get())

	# 先进后出
    # quque2=LifoQueue()
    # quque2.put(1)
    # quque2.put(2)
    # print(quque2.get())

	# 优先级队列
    quque3=PriorityQueue()
    quque3.put((1,'lqz'))
    quque3.put((100,'egon'))
    # 数字越小,优先级越高
    print(quque3.get())

进程池,线程池

# 线程池,进程池都在这个模块下concurrent.futures

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

# 进程池
# def task(n):
#     print(os.getpid(), '开始执行了')
#     time.sleep(random.random())
#     return n * n
#
#
# def callback(result):
#     print(result)
# if __name__ == '__main__':
#     # 开进程池去执行
#     # ProcessPoolExecutor实例化得到一个对象
#     pool_p=ProcessPoolExecutor(3)
#     # ll=[]
#     # for i in range(10):
#     #     # 把任务提交到进程池执行
#     #     f=pool_p.submit(task,n=i)
#     #     ll.append(f)
#     #
#     # # 等待所有子进程执行完成,主进程在执行
#     # pool_p.shutdown()
#     #
#     # for l in ll:
#     #     res=l.result()  # 取到当前进程执行任务的返回值
#     #     print(res)
#     #
#     # print('我是主进程')
#
#     # map取代for循环的,第一个参数是要执行的任务,第二个参数,是一个可迭代对象,迭代一次的结果,会传给任务
#
#
#
#     #   for i in range(10):
#     #     f=pool_p.submit(task,n=i)
#     # 等同于上面
#     # pool_p.map(task,range(10))
#     # pool_p.shutdown()
#     # print('主进程')
#
#
#     ## 回调
#     for i in range(10):
#         pool_p.submit(task,n=i).add_done_callback()


# 线程池
def task(n):
    print(os.getpid(), '开始执行了')
    time.sleep(1)
    return n * n


def callback(result):
    print(result.result())


# if __name__ == '__main__':
#     pool_p = ProcessPoolExecutor(3)
#	  ## 回调
#     for i in range(10):
#         pool_p.submit(task, n=i).add_done_callback(callback)
#
'''

submit(fn, *args, **kwargs) 异步提交任务

shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作
	wait=True,等待池内所有任务执行完毕回收完资源后才继续
	wait=False,立即返回,并不会等待池内的任务执行完毕
	但不管wait参数为何值,整个程序都会等到所有任务执行完毕
	submit和map必须在shutdown之前

result(timeout=None) 取得结果

map(func, *iterables, timeout=None, chunksize=1)(了解) 取代for循环submit的操作

add_done_callback(fn) 回调函数

done():判断某一个线程是否完成

cancle():取消某个任务

'''

if __name__ == '__main__':
    pool_p = ThreadPoolExecutor(3)
    for i in range(10):
        pool_p.submit(task, n=i).add_done_callback(callback)

协程介绍

协程是:程序级别的切换,单线程下实现并发,python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

greenlet模块(初级模块,实现了保存状态加切换)
# 安装第三方模块:在命令行下
# pip3 install greenlet
# pip3 uninstall greenlet  卸载第三方模块
# pip3 list   # 列出当前解释器环境下安装的第三方模块


from greenlet import greenlet



def eat(name):
    print(name,'在吃了一口')
    g2.switch(name)

    print(name,'在吃了第二口')
    g2.switch()


def play(name):
    print(name, '玩了一下')
    g1.switch()

    print(name, '玩了第二下')


g1=greenlet(eat)
g2=greenlet(play)

g1.switch('egon')


### 写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快
gevent模块
## gevent模块,协程模块,遇到io可以自动切换
# pip3 install gevent

import gevent
import time

# def eat(name):
#     print(name, '在吃了一口')
#     # 遇到了io
#     gevent.sleep(2)
#
#     print(name, '在吃了第二口')
#
#
# def play(name):
#     print(name, '玩了一下')
#     # 遇到了io,是gevent的io
#     gevent.sleep(3)
#     print(name, '玩了第二下')
#
#
# res1 = gevent.spawn(eat, 'egon')
# res2 = gevent.spawn(play, 'egon')
#
#
# ctime=time.time()
# # res1.join()
# # res2.join()  # 等地任务执行完成再执行下面那句
#
# gevent.joinall([res1,res2])   # 相当于上面那两句
# print('主线程')
# print(time.time()-ctime)

###使用原来的time的io,不会切,并且变成了串行
# def eat(name):
#     print(name, '在吃了一口')
#     time.sleep(2)
#
#     print(name, '在吃了第二口')
#
#
# def play(name):
#     print(name, '玩了一下')
#     time.sleep(3)
#     print(name, '玩了第二下')
#
#
# res1 = gevent.spawn(eat, 'egon')
# res2 = gevent.spawn(play, 'egon')
#
#
# ctime=time.time()
#
# gevent.joinall([res1,res2])   # 相当于上面那两句
# print('主线程')
# print(time.time()-ctime)

### time的io也要切换

# 猴子补丁:把原来的io全都替换成gevent的io
from gevent import monkey;monkey.patch_all()

def eat(name):
    print(name, '在吃了一口')
    time.sleep(2)
    print(name, '在吃了第二口')

def play(name):
    print(name, '玩了一下')
    time.sleep(3)
    print(name, '玩了第二下')

res1 = gevent.spawn(eat, 'egon')
res2 = gevent.spawn(play, 'egon')


ctime=time.time()

gevent.joinall([res1,res2])   # 相当于上面那两句
print('主线程')
print(time.time()-ctime)
asyncio
# 内置模块   python 3.4 推出这个模块,python作者主导的
import asyncio
import time
import threading
# 这个函数是协程函数
async def task():
    res=threading.current_thread().getName()
    print(res)
    print('xxx')
    await asyncio.sleep(2)
    print('协程执行完成')

async def task2():
    res=threading.current_thread().getName()
    print(res)
    print('2222')
    await asyncio.sleep(3)
    print('222协程执行完成')


ctime=time.time()
loop=asyncio.get_event_loop()

tasks=[task(),task2()]

loop.run_until_complete(asyncio.wait(tasks))
loop.close()
print(time.time()-ctime)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值