python中的进程,线程,协程以及io模型的总结

1 进程

    百度概念:

             进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

              其实也可以简单的理解成启动一个应用程序,就是启动了一个进程。比如QQ,酷我等。

     并行和并发:

               并行:是指多个程序同时执行,但是资源要足够使用,比如多核的cpu,是真正意义的同时执行

               并发:是指在资源有限的情况下,程序之间轮流的使用资源。但是轮转的时间非常的短,这段时                            间叫时间片

       同步,异步,阻塞,非阻塞:

                

              其实程序在启动之后,并没有立即执行。而是先进入就绪态,等待操作系统的调度,然后才进入到  运行态,如果入到io操作或者时间片到达等其他的导致无法执行的,就暂时挂起(阻塞),然后在进入到就绪态继续执行

            同步和异步:

                   同步就是一个任务的完成需要依赖另外一个任务的完成,比较可靠的任务序列,但是效率比较低

                   异步是不需要等待被依赖的任务完成的,只是通知依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了自己的任务,至于被依赖的任务最终能全部完成,它并不关心,所以相比较同步而言并不可靠,但是效率要高

           阻塞和非阻塞:

                      与程序或线程等待消息通知有关,也就是说阻塞和非阻塞是从线程或进程的消息通知角度来说的,而同步和异步是从线程和进程的执行方式来说的

            同步,异步和阻塞,非阻塞:

                      1 同步阻塞形式

                       2 异步阻塞

                      3 同步非阻塞

                      4 异步非阻塞

              python中的操作管理进程的mulitprocess模块:

                  

from multiprocessing import Process
import time

def func(args1,args2):
    print('*'*args1)
    time.sleep(1)
    print('*'*args2)
    return 1


if __name__ == '__main__':
    p_list=[]
    for i in range(10):
        p=Process(target=func,args=(2*i,4*i))
        p_list.append(p)
        p.start()


    #p.join()
    [p.join() for p in p_list]
    print('子进程全部结束')
        #p.join() #若不加join则变成了子进程间异步

                 使用类开启多线程等方式:

import os
from multiprocessing import Process

'''
第二中开启多线程方法  
 1 自定义类 继承Process
 2 类中必须实现一个run方法,实在子进程 中使用的

'''
class my_process(Process):

    def __init__(self,a,b):
        '''
        这里要调用父类的__init__
        因为下方要实现的函数内部可能要
        调用父类的方法
        比如下方的在调用self.pid的时候实际上是
        调用父类的属性 
        :param a:
        :param b:
        '''
        super().__init__()
        self.a=a
        self.b=b
    def run(self):
        '''
        在启动子进程的时候才会执行
        :return:
        '''
        print(self.a,self.b)
        print(self.pid)
        print(self.name)
        print('子',os.getpid())


if __name__ == '__main__':

    print('主',os.getpid())
    p1=my_process(1,2)
    #p1.start 实际上在内部调用了run方法
    p1.start()
    p2=my_process(4,5)
    p2.start()

  

                 守护进程:

 

'''


守护进程

子进程转化守护进程
   主进程结束 子进程也要结束
守护进程 回随着主进程的代码执行完毕 而结束




'''

from multiprocessing import Process
import time


def func():
    while True:
        time.sleep(0.2)
        print("子进程转化为守护进程")
def func1():
    print('子进程start')
    time.sleep(8)
    print('子进程end')

if __name__ == '__main__':
    '''
    p1为主进程的守护进程p2为子进程
    
    守护进程只会关注主进程是否结束
        比如主进程代码结束后,p1就回结束
        但是p2还没有结束 但是p1已经随着主进程的结束已经不执行了
    
    而父进程会关注子进程是否已经结束
        父进程已经结束了 但是p2还没有结束
        父进程回等待p2的结束而结束
        这时候父进程的代码已经执行完毕了
        所以守护进程也不结束了


在主进程内结束一个子进程 p.terminate()
     结束一个进程不是在执行方法之后立即生效 需要操作系统相应的过程
检查一个进程是否还存在的状态 p.is_alive()
p.name p.pid p.getpid 分别是获取进程的名字 进程号 以及父进程
    
    
    '''
    p1=Process(target=func)
    p1.daemon=True  #设置子进程为守护进程
    p1.start()
    p2=Process(target=func1)
    p2.start()
    print(p1.is_alive())#检查进程是否还存在还活着
    print(p2.name)    #获取一个进程的名字
    p2.terminate()  #结束一个进程
    # 这个时候应该是True
    # 因为上一句代码发结束进程指令发给了操作系统
    # 操作系统还需要时间关闭该进程
    p2.is_alive()
    i=0
    while i<5:
        print('我是socket,server',i)
        time.sleep(1)
        i+=1
    p2.join()
    print(p1.is_alive())
    print(p2.is_alive())

  进程之间的通讯ipc,进程之间的数据是隔离的。通过锁,可以保证数据的安全性,但是牺牲了效率

from multiprocessing import Process
from multiprocessing import Lock
import json
import time

def show(i):
    with open('ticket') as f:
        json_msg=json.load(f)
        print('%s余票:%s' % (i,json_msg['ticket']))

def buy_ticket(i,locks):
    locks.acquire()  #拿钥匙
    with open('ticket') as f:
        json_msg=json.load(f)
    if json_msg['ticket']>0:
        json_msg['ticket']-=1
        print('%s买到票了'%i)
    else:
        print('没有买到')
    time.sleep(0.1)
    with open('ticket','w')as f1:
        json.dump(json_msg,f1)
    locks.release()  #还钥匙
if __name__ == '__main__':
    for i in range(10):
        p1=Process(target=show,args=(i,))
        p1.start()
    locks=Lock()  #生成锁对象
    for a in range(10):
        p2=Process(target=buy_ticket,args=(a,locks))
        p2.start()

  信号量 Semaphore  和锁的概念很像,可以看成多把锁

               

from multiprocessing import Semaphore
from multiprocessing import Process
import time
import random

def ktv(i,sem):
    sem.acquire()
    print('%s走进ktv'%i)
    time.sleep(random.randint(1,5))
    print('%s走出ktv'%i)
    sem.release()


if __name__ == '__main__':
    sem=Semaphore(4)
    for i in range(20):
        p=Process(target=ktv,args=(i,sem))
        p.start()

  进程中的事件(Event),通过一个信号来控制多个进程的同时执行或则阻塞。即使可以理解成车辆过红绿灯,像python中的性能测试框架locust 就用到了事件。

import time,random
from multiprocessing import Process,Event
def car(e,i):
    '''
    通过判断事件的状态,来决定是否阻塞

    :param e:事件对象
    :param i: 循环变量
    :return:
    '''
    if not e.is_set():
        print('car%s在等待'%i)
        e.wait()
    print('car%s通过'%i)


def light(e):
    '''
    通过判断事件的真假
    来更改事件的状态
    :param e: 事件对象
    :return:
    '''
    while True:
        if e.is_set():
            time.sleep(2)
            e.clear()
            print('\033[31m红灯亮了\033[0m')
        else:
            time.sleep(2)
            e.set()
            print('\033[32m绿灯亮了\033[0m')

if __name__ == '__main__':
    e=Event()
    traffic=Process(target=light,args=(e,))
    traffic.start()
    for i in range(10):
        ca=Process(target=car,args=(e,i,))
        ca.start()
        time.sleep(random.random())

  进程间的通讯--->队列。特点是先进先出,在python中如果队列中已经满了或者空了那么put和get回阻塞等待数据。

'''

生产者和消费则模型


买包子               包子笼          买包子
生产数据快-----------容器-----------处理数据慢
'''

from multiprocessing import Process,Queue
import time,random
def product(name,food,q):
    for i in range(10):
        foods='%s生产了%s%s'%(name,food,i)
        print(foods)
        q.put(foods)
        time.sleep(2)

def commu(q,name):
    '''
    注意 队列的进程安全 即一个队列的数据同时只能被一个进程取走
    主进程有2个消费者的子进程,所以如果只在主进程put一个None
    只有一个消费者进程能去到None,所以只能结束一个
    消费者子进程 而另外一个消费者子进程取不到值
    就进入阻塞状态

    :param q:
    :param name:
    :return:
    '''
    while True:
        food=q.get()
        if food==None:
            print('已经取空')
            break
        print('\033[31m{}吃了{}\033[0m'.format(name,food))
        time.sleep(1.3)
if __name__ == '__main__':
    q=  Queue()
    p1=Process(target=product,args=('yuan','baozi',q,))
    p1.start()
    p2 = Process(target=product, args=('xu', 'hulatang', q,))
    p2.start()
    c1=Process(target=commu,args=(q,'dandan',))
    c1.start()
    c2= Process(target=commu, args=(q, 'wangyongmei',))
    c2.start()
    p1.join()
    p2.join()
    # #注意 队列的进程安全 即一个队列的数据同时只能被一个进程取走
    # 主进程有2个消费者的子进程,所以如果只在主进程put一个None
    # 只有一个消费者进程能去到None,所以只能结束一个
    # 消费者子进程 而另外一个消费者子进程取不到值
    # 就进入阻塞状态
    q.put(None)
    q.put(None)

  队列保证了数据的安全性,但是消费者无法感知生产者是否已经生产完成,当然可以通过将生产者设定为守护进程,随着主进程的结束而结束,但是python中为我们提供了 JoinableQueue 这种队列。原理呢

就是生产者生产一个数据就count+1,而消费者消费一个就count-1

'''
解决生产者和消费者的问题
    消费者无法感知生产者是否已经真正的结束
    假如 在已经被取空的时候,恰好这时生产者
    正要往队列中加数据
JoinableQueue
      q.task_done()
      q.join()

在消费者这一端
   每次获得一个数据
   处理一个数据
   发送一个记号 标志一个数据被处理成功 q.task_done()
在生产者这一端:
   每次生产一个数据
   且每一次生产的数据都放在队列中
   在队列中刻上一个记号
   当生产者全部生产完毕后
   join信号 已经停止生产数据了
            切要等待之前被刻上记号都消费完了结束阻塞

'''
import time
import random
from multiprocessing import Process,JoinableQueue,Queue

def conmmu(q,name):
    while True:

        food=q.get()
        if food=='None':
            print('%s取到一个空')
            break
        print('%s吃到了%s'%(name,food))
        time.sleep(random.randint(1,3))
        print('xiaofeizhe1')
        q.task_done()  #count-1
        print('xiaofeizhe2')

def prodect(q,name,food):
    for i in range(4):
        time.sleep(random.randint(1,3))
        foods='%s做的第%s个%s'%(name,i,food)
        q.put(foods)
    print('shengchanzhe1')
    q.join()  #阻塞 知道一个队列中的所有数据被执行完毕
    print('shengchanzhe2')



if __name__ == '__main__':
    q=JoinableQueue()
    p1=Process(target=prodect,args=(q,'yuan','baozi',))
    p2=Process(target=prodect,args=(q,'xu','hulatang',))
    '''
    其实这里也可以将消费者者设定为守护进程
    '''

    c1=Process(target=conmmu,args=(q,'wang',))
    c2=Process(target=conmmu,args=(q,'dan'))

    # c1.daemon=True
    # c2.daemon = True
    p1.start()
    p2.start()
    print('zhujincheng c1')
    c1.start()
    c2.start()
    print('zhujincheng c2')
    p1.join()
    p2.join()
    # time.sleep(19)
    # print(p1.is_alive())
    # print(p2.is_alive())
    print('zhu')
'''
prodect函数中的q.join() 只是感知队列中的数据是否被执行完毕,
此时是处于阻塞状态的,若被执行完毕则完成代码,这里陷入死循环
是因为conmmu函数中有while循环,所以需要配合守护进程,


p1.join()确保了prodect函数中的q.join()确实已经被执行完毕,
如果不在主进程中加p1.join()和p2.join 则两个生产者子进程和主进程就是异步的关系
随着主进程的结束,那么守护进程也会结束 但是消费者子进程可能还没有将生产者子进程
生产的数据执行完成就被关闭了 造成了生产者子进程的q.join()一直在哪里阻塞

conmmu函数中的 q.task_done()每执行一次就回向q.join()发送一次标记


'''

  进程之间的数据共享  Manager

'''
数据共享Manager

基于消息传递的并发变成是大势所趋

即使是使用线程 推荐做法也是将程序设计为独立的线程集合,通过消息队列交换数据
这样极大地减少了对使用锁定和其他同步手段的需求 还可以扩展到分布式系统中

但是进程间应该尽量避免通信,即便通信,也应该选择进程安全的工具来避免加锁带来的问题

数据不安全的根本 多进程之间抢占资源

'''

from multiprocessing import Manager,Process,Lock

def man_dict(dic,locks):
    locks.acquire()
    dic['count']-=1
    locks.release()
    print('子进程---',dic)

if __name__ == '__main__':
    m=Manager()
    locks=Lock()
    p_list=[]
    dicts=m.dict({'count':100})
    for i in range(50):
        p=Process(target=man_dict,args=(dicts,locks))
        p.start()
        p_list.append(p)
    [a.join() for a in p_list]
    print('主进程---',dicts)

  进程池Pool:

             我们知道每开启一个进程,都要开启属于这个进程的内存空间包括(寄存器 堆栈 文件等)。

        进程池和信号量的区别:

              信号量是进程等待排队,多个进程已经被创建 等待执行代码

               进程池是任务等待排队,固定数量的级才能拿已经被创建,任务每次执行就调用固定的进程,减                            少了开启进程的时间消耗。

'''
p=Pool
p.map(funcname,iterable)  默认异步的执行任务,且自带close和join

p.apply(funcname,args)    同步调用的

p.apply_async(funcname,args)
异步调用和主进程完全异步
如果不需要可以通过p.close()  p.join()来关闭进程池链接 等待进程池内任务执行完毕






进程池的返回值

1 队列
2 直接返回值接收




'''
from multiprocessing import Pool
import time

def func(i):
    time.sleep(0.5)
    return i*i

if __name__ == '__main__':
    p=Pool(5)

    #同步提交
    for i in range(10):
        res=p.apply(func,args=(i,))
        print(res)


    #
    # for i in range(10):
    #     res=p.apply_async(func,args=(i,))
    #     #res 打印的是异步调用后的对象
    #     print(res)
    #     #res.get() 打印的是异步调用后的结果
    #     # 但是在res=p.apply_async(func,args=(i,))
    #     # func并没有立刻返回结果 所以在res.get()
    #     # 就会阻塞在这里 等待结果 同时在表现形式 为 同步
    #     print(res.get())

    #异步解决方法
    # start_time1=time.time()
    # p_lis=[]
    # for i in range(10):
    #     res=p.apply_async(func,args=(i,))
    #     p_lis.append(res)
    #
    # print([a.get() for a in p_lis])
    # end_time=time.time()-start_time1
    # #异步解决方法
    # start_time2=time.time()
    # res=p.map(func,range(10))
    # print(res)
    # end_time2=time.time()-start_time2
    # print(end_time,end_time2)

    #map和apply_async的区别
    #map是一次性返回所有数据 apply_async一次返回进程池中数量一点一点的返回
    #执行效率上 map稍高 但是apply_async分次返回 节省内存

  进程池的回调函数 callback

'''


回调函数


'''

from multiprocessing import Pool
import os

def func1(n):

    print('in func1 %s'%n,os.getpid())
    return n*n
def func2(nn):
    print('in func2 %s' % nn,os.getpid())
    print(nn)

if __name__ == '__main__':
    p=Pool(5)
    print('主进程',os.getpid())
    #callback 回调函数  主进程中执行
    #将func1的返回值作为入参费func2  回调函数不能传参数
    for i in range(10):
        p.apply_async(func1,args=(i,),callback=func2)

    p.close()
    p.join()

  回调函数 在爬虫中的应用

import requests
from multiprocessing import Pool
from urllib.request import urlopen

def open_msg(url):
    ret=urlopen(url)
    return ret.read().decode('utf-8')

def get_msg(url):
    '''
    子进程请求函数
    :param url:
    :return:
    '''
    res_msg = requests.get(url)
    if res_msg.status_code==200:
        return url,res_msg.content.decode('utf-8')
def re_msg(args):
    '''
    主进程回调函数
    :param args:
    :return:
    '''
    url,content=args
    print(url,len(content))

if __name__ == '__main__':
    url_list=[
        'http://www.cnblogs.com/',
        'http://www.baidu.com',
        'https://www.sogou.com',
        'http://www.sohu.com'
    ]
    p=Pool(5)
    for i in url_list:
        p.apply_async(get_msg,args=(i,),callback=re_msg)
    p.close()
    p.join()

  

2 线程

          百度概念:

               是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

             线程的属性:

          1 轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的,能保证独立运行的资源
线程的实体包括程序 数据和TCB。线程是动态概念,他的动态特性由线程控制块TCB(thread Control Block)

2 独立调度和分派的基本单位
在多线程os中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很 轻 故线程的切换
非常迅速且开销小(在同一进程中的)
3 共享进程资源
线程在同一进程中的各个线程,都可以共享该进程所拥有的资源。这首先表现在:所有的线程都具备相同的进程id
这意味着,线程可以访问该进程的内一个内存资源。此外,还可以访问进程所有拥有的已打开文件 定时器 信号量机构
等。由于统一进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核
4 可并发执行
在一个进程中的多个线程中,可以并发执行,甚至允许一个进程中所有的线程都能并发执行。同样,不同进程中的线程也能并发执行。
充分利用和发挥了处理机与外围设备的并行工作的能力

   全局解释器锁GIL:
python代码执行由python虚拟机(也叫解释器主循环)来控制。python在设计之初就考虑到要在主循环中,同时只有一个线程在执行
虽然python解释器中可以运行多个线程。但是在任意时刻只能有一个线程在解释器中运行
对python虚拟机的访问由全局解释器锁GIL控制,正是这个锁能保证统一时刻只能有一个线程在运行
a 设置gil
b 切换到一个线程去运行
c 运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0))
d 把线程设置为睡眠状态
e 解锁gil
f 再次重复以上所有步骤
      高cpu  计算类  ----高cpu利用率
高io input time.sleep
爬去网页
qq聊天
处理web请求
读写数据库

高cpu 多进程
高io 多线程
python中的threading:
from threading import Thread
import time,os

#第一种启动多线程方法
# def func(n):
#     time.sleep(1)
#     print(n)
# #if __name__ == '__main__':
#
# for i in range(10):
#     t=Thread(target=func,args=(i,))
#     t.start()

#第二种启动多线程方法

# class mythread(Thread):
#     def __init__(self,arg):
#         super().__init__()
#         self.arg=arg
#     def run(self):
#         time.sleep(1)
#         print(self.arg)
#
# t=mythread(10)
# t.start()


#多线程并发

# def func(a,b):
#     n=a+b
#     print(n,os.getpid())
# print('主线程',os.getpid())
# for i in range(10):
#     t=Thread(target=func,args=(i,5))
#     t.start()

#内存数据的共享问题

def func(a,b):
    global g
    g=0
    print(g,os.getpid())
g=100
t_lis=[]
for i in range(10):
    t=Thread(target=func,args=(i,5))
    t.start()
    t_lis.append(t)
[a.join() for a in t_lis]
print(g)
 

  

 守护线程:


'''

线程是进程中的执行单位
线程是cpu执行的最小单位
线程之间资源共享
线程的开启和关闭以及切换的时间开销远远小于进程
线程本身可以在同一时间使用多个cpu

python与线程
  cpython解释器在解释代码过程中容易产生数据不安全的问题
  gil全局解释器锁  锁的是线程

threading

守护线程
   守护进程会随着主进程代码的执行结束而结束
   守护线程会在主线程结束后之后等待其他子线程的结束才结束

  无论是进程还是线程,都遵循 守护xx回等待主xx运行完毕后被销毁
  需要强调的是 运行完毕并非终止运行
  对主进程来说 ,运行完毕指的是主进程代码运行完毕
  对主线程来说,运行完毕指的是主线程所在进程内所有非守护线程统统运行完毕,主线程才算运行完毕


  主进程在其代码结束后就已经算运算完毕了(守护进程在此时就被回收)然后主进程
  回一直等待非守护的子进程都运行完毕后。回收子进程的资源(否则回产生僵尸进程),才回结束

   主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束异味着进程的结束
   进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束

也就是说  守护线程和守护进程都会随着被守护的线程和进程代码执行结束而结束,
但是 主线程回进程内其他非守护线程结束,才会结束,就算主线程的代码执行完毕
也只是在阻塞等待,一个进程内必须要有一个主线程,主线程的结束意味着该进程结束

'''

#守护线程

from threading import Thread
import time
def func1():
    print('start守护进程')
    time.sleep(10)
    print('end守护进程')
def func2():
    print('ok')
    time.sleep(3)
if __name__ == '__main__':

    t=Thread(target=func1,)
    t.daemon=True
    t.start()
    t1=Thread(target=func2,)
    t1.start()

    print('主线程')
    #打印结果很明显守护线程随着
    # 主线程和子线程的代码结束,
    # 守护线程中的最后一句没有打印
    # 意味着没有守护线程随着同进程中其他非守护
    # 线程的结束而结束
 

 线程锁

from threading import Thread,Lock
import time

# def func(lock,i):
#     lock.acquire()
#     print('第%s个'%i)
#     time.sleep(0.5)
#     lock.release()
#     pass

def func(lock):
    lock.acquire()
    global n
    temp=n
    time.sleep(0.2)
    n=temp-1
    lock.release()


if __name__ == '__main__':
    print(__name__)
    n=10
    t_lst=[]
    locks=Lock()
    '''
    多线程本身就收到gil锁的控制,但是gil锁是针对的线程
    并不能保证其他线程几乎同时访问同一个数据。
    所以要加锁,变异步为同步,降低了效率但是保证了数据的安全行
    
    '''
    # for i in range(10):
    #     t=Thread(target=func,args=(locks,i))
    #     t.start()
    for i in range(10):
        t=Thread(target=func,args=(locks,))
        t.start()
        t_lst.append(t)
    [a.join() for a in t_lst]
    print(n)

  死锁问题,解决方法递归锁:

'''

死锁
科学家吃面 死锁问题 三个人吃面,一碗面一个叉子,a拿走了面,b拿走了叉子
,造成僵持

互斥锁 和 递归锁
互斥锁 Lock 用来决绝数据安全问题
递归锁 用来解决死锁问题  原理 通俗的将 是将所有钥匙放入钥匙串,在拿的时候,会将所有钥匙都拿走,但是还的时候要都换回来
'''

from threading import Lock,Thread,RLock
import time


# noodle_lock=Lock()
# fork_lock=Lock()
# def eat1(name):
#     noodle_lock.acquire()
#     print('%s拿到面条'%name)
#     fork_lock.acquire()
#     print('%s拿到叉子' % name)
#     print('%s吃面'%name)
#     fork_lock.release()
#     noodle_lock.release()
#
# def eat2(name):
#     fork_lock.acquire()
#     print('%s拿到叉子' % name)
#     time.sleep(1)
#     noodle_lock.acquire()
#     print('%s拿到面条' % name)
#     print('%s吃面'%name)
#     noodle_lock.release()
#     fork_lock.release()
#
# Thread(target=eat1,args=('yuan0',)).start()
# Thread(target=eat2, args=('yuan1' ,)).start()
# Thread(target=eat1,args=('yuan2',)).start()
# Thread(target=eat2, args=('yuan3' ,)).start()

'''
递归锁  一串钥匙  同时拿到2把钥匙,因为这2把钥匙都在一个钥匙串上  用来解决死锁问题
'''
# fork_lock=noodle_lock=RLock()
#
# def eat1(name):
#     noodle_lock.acquire()
#     print('%s拿到面条'%name)
#     fork_lock.acquire()
#     print('%s拿到叉子' % name)
#     print('%s吃面'%name)
#     fork_lock.release()
#     noodle_lock.release()
#
# def eat2(name):
#     fork_lock.acquire()
#     print('%s拿到叉子' % name)
#     time.sleep(1)
#     noodle_lock.acquire()
#     print('%s拿到面条' % name)
#     print('%s吃面'%name)
#     noodle_lock.release()
#     fork_lock.release()
#
# Thread(target=eat1,args=('yuan0',)).start()
# Thread(target=eat2, args=('yuan1' ,)).start()
# Thread(target=eat1,args=('yuan2',)).start()
# Thread(target=eat2, args=('yuan3' ,)).start()


fork_lock=RLock()
#
def eat1(name):
    fork_lock.acquire()
    print('%s拿到面条'%name)
    fork_lock.acquire()
    print('%s拿到叉子' % name)
    print('%s吃面'%name)
    fork_lock.release()
    fork_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子' % name)
    time.sleep(1)
    fork_lock.acquire()
    print('%s拿到面条' % name)
    print('%s吃面'%name)
    fork_lock.release()
    fork_lock.release()

Thread(target=eat1,args=('yuan0',)).start()
Thread(target=eat2, args=('yuan1' ,)).start()
Thread(target=eat1,args=('yuan2',)).start()
Thread(target=eat2, args=('yuan3' ,)).start()

  线程中的信号量:

'''

信号量  Semaphore

'''

import time
from threading import Semaphore,Thread

def func(sema,a,b):
    sema.acquire()
    time.sleep(1)
    print(a,b,a+b)
    sema.release()

sem=Semaphore(4)
for i in range(10):
    t=Thread(target=func,args=(sem,i,i+5))
    t.start()

  

线程中的事件
'''
线程 事件

链接数据库
  检查数据库的可链接情况
     1 能够更方便的对数据进行怎删改查
     2 安全访问的机制

'''
from threading import Thread,Event
import time,random
def connect_db(e):
    cont=0
    while cont<3:
        e.wait(1)
        if e.is_set():
            print('链接数据库成功')
            break
        else:
            cont += 1
            print('第%s次链接数据库失败'%cont)
    else:
        raise TimeoutError("数据库链接超时")


def check_web(e):
    time.sleep(random.randint(0,10))
    e.set()
if __name__ == '__main__':
    e=Event()
    t1=Thread(target=check_web,args=(e,))
    t1.start()
    t2=Thread(target=connect_db,args=(e,))
    t2.start() 

线程中条件
Condition 条件变量
'''
条件   Condition()
 更复杂的锁

 acquire release
 一个条件被创建之处,默认有一个False状态
 False状态,回影响wait一直处于等待状态 等待notify
 notify(int) 制造几把钥匙
'''
# from threading import Thread,Condition
#
# def func(con,i):
#     con.acquire()
#     con.wait()
#     print('在第%s个循环里'%i)
#     con.release()
#     print('%s还钥匙'%i)
# con=Condition()
# for i in range(10):
#     Thread(target=func,args=(con,i)).start()
#
# while True:
#     num=int(input('>>num'))
#     con.acquire()
#     con.notify(num)
#     con.release()
'''
Condition(条件变量)通常与一个锁关联。需要在多个Contidion中共享一个锁时,
可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。

可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,
池中的线程处于状态图中的等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;
得到通知后线程进入锁定池等待锁定。

Condition():

acquire(): 线程锁
release(): 释放锁
wait(timeout): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。
wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。
notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,
默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。
notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。
notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程


'''
import time
from threading import Condition,Thread

'''
Condition 条件变量 其实底层还是应用的lock,在子进程获取锁之后,执行了wait之后就被挂起,等待notify
'''

class producer(Thread):
    def __init__(self):
        super().__init__()
    def run(self):
        global num
        con.acquire()
        while True:
            print('开始添加鱼丸')
            num+=1
            print('锅里的鱼丸个数:%d'%num)
            time.sleep(1)
            if num>=5:
                print('锅里已经有了5个鱼丸,不在添加')
                # 唤醒等待的线程 就是发送给consumers的wait方法
                con.notify()
                #等待被唤醒,这里是等待consumers的notify方法
                con.wait()
        con.release()

class consumers(Thread):
    def __init__(self):
        Thread.__init__(self)
    def run(self):
        con.acquire()
        global num
        while True:
            print('开始吃拉')
            num-=1
            print('锅里剩余的鱼丸数量:%d'%num)
            time.sleep(2)
            if num==0:
                print('锅里没有丸子了加丸子')
                #唤醒等待的线程 就是发送给producer的wait方法
                con.notify()
                con.wait()
        con.release()
if __name__ == '__main__':
    con = Condition()
    num = 0
    p=producer()
    c=consumers()
    p.start()
    c.start()

  进程中的队列

'''

线程中的队列
queue
'''
# 直接导入就是线程的队列,进程的需要从Processing
import queue

# q=queue.Queue(10)
# for i in range(10):
#     q.put(i)
# #q.put_nowait(1)队列满的时候回报错 而不是像put一样等待
# print(q.qsize())
# for a in range(11):
#     print(q.get())
#q.get_nowait() 队列取空的时候回报错 而不是像get一样阻塞等待

# LifeQueue  像栈  先进后出
q=queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())

#优先级队列  数字越小优先级越高 可以为负
# 如果存在优先级别一样 则按照值的ascii排列
q1=queue.PriorityQueue()
q1.put((20,'a'))
q1.put((10,'b'))
q1.put((30,'c'))
q1.put((10,'d'))
print(q1.get())

 python中线程池  concurrent 模块

'''
线程池
   concurrent.futures模块提供了高度封装的异步调用接口
   ThreadPoolExecutor 线程池 提供异步调用
   ProcessPoolExecutor 进程池,提供异步调用

   #2 基本方法
#submit(fn, *args, **kwargs)
异步提交任务

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

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

#result(timeout=None)
取得结果

#add_done_callback(fn)
回调函数

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

# cancle()
取消某个任务
'''
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor
import time,random

def func(n):
    time.sleep(2)
    print(n)
    return n*n

def call_back(a):
    print('结果是%s'%a.result())
#多进程的一般不超过cpu个数*5
t=ThreadPoolExecutor(max_workers=5)
t_lst=[]
# for i in range(20):
#     t1=t.submit(func,i)   #类似于t.apply_ascny
#     t_lst.append(t1)
# t.shutdown(True)       #类似于 t.close()+t.join()  如果返回值过多的话可以不阻塞
# for a in t_lst:
#     a.result()
for i in range(20):
    t.submit(func,i).add_done_callback(call_back)

#t.map(func,range(10))#如果使用map的话就拿不到 结果

  

         

3 协程

       概念:          

                           本质上是一个线程 能够在多任务之间切换来节省一些io时间协程中任务之间的切换也消耗时间,但是开销                    要远远小于 进程线程之间的切换

         greenlet模块:

                  

# def eat():
#     print('start eat')
#     g2.switch()  #切换 且有记录上次代码执行的位置
#     print('end eat')
#     g2.switch()
#
# def play():
#     print('og')
#     g1.switch()
#     print('end og')


# g2=greenlet(play)
# g1=greenlet(eat)
# # 切换
# g1.switch()
线程的效率 对比
比如2个任务,一个任务中有io操作,就可以用swich切换到另外一个任务执行
等前一个任务的io结束就可以 在切换回来 是线程不空闲

进程+线程+协程
操作系统 调度进程+线程
协程之间的程序 由程序完成。例如join。只有遇到协程模块认识的io操作的时候,程序才会切换
实现并发的效果

  

        gevent 模块:

           

'''


同步和异步

协程
    能够在一个线程中  实现并发效果的概念
    能够规避一些任务中的io操作
    在任务的执行过程中,检测到io就切换到其他任务

'''

from gevent import monkey;monkey.patch_all()
import time
import gevent
def time_check(func):
    def inner():
        start_time=time.time()
        func()
        end_time=time.time()-start_time
        print(func,end_time)
    return inner

def task():
    time.sleep(1)
    print(12345)
@time_check
def sync():
    for i in range(10):
        task()
@time_check
def asyn():
    g_lst=[]
    for i in range(10):
        g=gevent.spawn(task)
        g_lst.append(g)
    #[a.join() for a in g_lst]
    gevent.joinall(g_lst)

#通过 测试效率 发现异步采用协程比同步 高了10倍左右
sync()
asyn()

  协程在爬虫之中的应用

from gevent import monkey;monkey.patch_all()
import time
import gevent
import requests
from urllib.request import urlopen

# url='http://www.baidu.com'
# res1=urlopen(url)
# res2=requests.get(url)
# print(res1.read().decode('utf-8'))#有格式
# print(res2.content.decode('utf-8'))#无格式

def get_url(url):
    res1 = urlopen(url)
    cont=res1.read().decode('utf-8')
    return len(cont)

url_list=[ 'http://www.baidu.com',
           'http://www.sogou.com',
            'http://www.taobao.com',
            'http://www.hao123.com',
            'http://www.cnblogs.com'
          ]
# g_list=[]
# start_time=time.time()
# for a in url_list:
#     g=gevent.spawn(get_url,a)
#     g_list.append(g)
# gevent.joinall(g_list)
# v=[v.value for v in g_list]
# end_time=time.time()-start_time
# print(end_time)
# print(v)
start_time=time.time()
g1=gevent.spawn(get_url,'http://www.baidu.com')
g2=gevent.spawn(get_url,'http://www.sogou.com')
g3=gevent.spawn(get_url,'http://www.hao123.com')
g4=gevent.spawn(get_url,'http://www.taobao.com')
g5=gevent.spawn(get_url,'http://www.cnblogs.com')
gevent.joinall([g1,g2,g3,g4,g5])
end_time=time.time()-start_time
print(end_time)
print(g1.value)
print(g2.value)
print(g3.value)
print(g4.value)
print(g5.value)

  协程在socket的应用

            server端

from gevent import monkey
monkey.patch_all()
import socket
import gevent

'''
python的多线程被gil弱化了
协程在一个线程上 提高了cpu的利用率
协程相比多线程的优势  切换的效率更高

'''
def sock_server(con):
    '''
    使用gevent 必须要将
    from gevent import monkey
    monkey.patch_all() 导入
    不然gevent不认识socket的阻塞操作
    :param con:
    :return:
    '''
    while True:
        msg=con.recv(1024).decode('utf-8')
        print(msg)
        con.send(b'hello client')
        con.close()

if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1', 10090))
    sk.listen()
    while True:
        con, addr = sk.accept()
        gevent.spawn(sock_server,con)
        print(123)
        con.close()
    sk.close()

         client端:

import socket

ck=socket.socket()
ck.connect(('127.0.0.1',10090))
while True:
    ck.send(b'hello server')
    msg=ck.recv(1024)
    print(msg)
ck.close()

  

4 IO模型

'''
I/O模型
同步(synchronous)I/O
异步(asynchronous)I/O
阻塞(blocking)I/O
非阻塞(non-blocking)I/O

liunx 的network I/O

blocking IO        阻塞io
nonblocking IO     非阻塞io
IO multiplexing     io多路复用
signal driven IO     信号驱动io
asynchronous IO      异步io
由signal driver IO(信号驱动IO)在实际中并不常用,所有主要介绍其余四中IO model

等待数据准备(Waiting for the data to be ready)
将数据从内核拷贝到进程中(Copying the data form the kernel to the process)
io io模型的区别就是在以上两个阶段各有不同的情况

同步
  提交一个任务之后要等待这个任务执行完毕

异步
   直观提交任务,不等待这个任务执行完毕就可以做其他的事情

阻塞
    revc recvfrom accept

非阻塞


阻塞
  线程   运行状态--->阻塞状态--->就绪---->执行
非阻塞
   线程   运行完毕

IO多路复用
   select机制     win linux  都是系统轮询每一个被监听的项看是否有读操作
   poll机制        linux   它可以监听的对象比select机制可以监听的多
      以上两项 随着监听系那个的增多,导致效率降低


   epoll机制       linux  相当于给列表内每个对象绑定了一个回调函数
                   每当监控对象有信息返回,该会调函数就会主动通知
                   程序调用
'''


#阻塞io模型

#io多路复用

# 异步io
    #只需要发一个  异步的调用读操作  交给操作系统 让操作系统去等待 这时候线程可以去干其他的事情
    #  著名的异步框架   tornado  twstied

  

阻塞io:

非阻塞io-----socket:

         server:

import socket


#非阻塞io失败的例子
# sk=socket.socket()
# sk.bind(('127.0.0.1',10066))
# #设置为非阻塞 默认是阻塞
# sk.setblocking(False)
# sk.listen()
# # 如果没有收到链接就一直阻塞,让出了cpu控制权
# #sk.accept()
# while True:
#     try:
#         #sk.setblocking(False)之后
#         #收不到链接不但阻塞并且报错
#         #recv同理
#         conn,addr=sk.accept()
#         if conn:
#             print('建立链接')
#             msg=conn.recv(1024)
#             print(msg)
#     except BlockingIOError:
#         pass


sk=socket.socket()
sk.bind(('127.0.0.1',10066))
#设置为非阻塞 默认是阻塞 把socket当中需要阻塞的方法都改变成非阻塞 recv recvfrom accpet
sk.setblocking(False)
sk.listen()
# 如果没有收到链接就一直阻塞,让出了cpu控制权
#sk.accept()
con_lista=[]
del_lista=[]
while True:
    try:
        #sk.setblocking(False)之后
        #收不到链接不但阻塞并且报错
        #recv同理
        conn,addr=sk.accept()
        print('建立链接',addr)
        # 要将链接存入列表,这是因为client有网络延迟
        # 本机执行代码速度远远快过网络延迟,如果recv放在这里回报错
        # 且恰好这是再有人链接server,conn会e被内存冲掉,就找不到当时和
        # server链接的通道
        con_lista.append(conn)
    except BlockingIOError:
            for a in con_lista:
                try:
                    #这里为什么将recv放在这里呢
                    #如果是非阻塞模式,那么收不到信息会报错
                    #这是因为client端有网络延迟,但是代码在本机会执行的
                    #很快所以必须要放在下边
                    msg = a.recv(1024)
                    if msg==b'':
                        # 如果客户端断开链接那么 接到的信息是byte类型的空信息
                        # 那么就将链接加入到del_lista列表中,这样就保存了准备删除的链接
                        del_lista.append(a)
                        continue
                    print(msg)
                    a.send(b'byebye')
                except BlockingIOError:
                    pass
            #等待循环结束之后 由于del_lista已经储存了将要删除的链接,则就可以循环删除
            for conn in del_lista:
                conn.close()
                con_lista.remove(conn)
            #同时清空del_lista列表中的元素
            del_lista.clear()

'''
非阻塞io一般不推荐
优点 :
     能够在等待任务完成的时间里干其他活 (包括提交其他任务,也就是后台 可以有多个任务在同时执行)

缺点:
    1 循环调用recv()将大幅推高cpu占用率,这也是我们在代码中留一句time.sleep()的原因,否则在低配主机下极容易出现卡机情况
    2 任务完成的响应延迟增大了,因为每过一段时间才去轮训一次read操作,而任务可能在两次轮询之间的任意时间完成,这回导致整体
    数据吞吐量的降低

此外 在这个方案占用recv()更多的是起到检测 操作是否完成  的作用,实际操作系统提供了更为高效的检测 操作是否完成 作用的接口
例如select()多路复用模式,可以一次检测多个链接是否活跃

'''

  

      client:

import socket
import time
from threading import Thread

# ck=socket.socket()
# ck.connect(('127.0.0.1',10066))
# ck.send(b'hello')
# msg=input('client>>>').encode('utf8')
# ck.send(msg)
# time.sleep(0.1)
# ck.send(b'hello')
# ck.close()
def func():
    ck=socket.socket()
    ck.connect(('127.0.0.1',10066))
    ck.send(b'hello')
    time.sleep(1)
    print(ck.recv(1024))
    ck.close()

for i in range(20):
    Thread(target=func).start()

  IO多路复用:

'''
io multiplexing 也被成为事件驱动io  select/epoll的好处就在于单个process就可以同时处理多个网络链接的io
他的基本原理就是select/epoll这个function回不断的轮训所负责的所有socket 当某个socket有数据到到达了,就通知
用户进程   但是如果连接数较少的话还不如阻塞io效率高


'''

import select
import socket


sk=socket.socket()
sk.bind(('127.0.0.1',10088))
sk.setblocking(False)
sk.listen()
read_lista=[sk]
while True:
    #r_lst 里边只会有存 client给我链接的对象
    # 所以第一次循环 只会有sk也就是socket对象,因为sk对象接到了client的链接请求
    # 第二次client发送了信息,是给conn的也就是链接的
    # 所以r_lst中只会有conn的对象,能读的加载到r_lst
    print('io111')
    # 在select处阻塞
    r_lst,w_lst,x_lst=select.select(read_lista,[],[])
    print('io222')
    #r_lst内返回的实际上是socket对象
    print(r_lst)
    for i in r_lst:
        if i == sk:
            conn,addr=i.accept()
            read_lista.append(conn)
        else:
            msg=i.recv(1024).decode('utf-8')
            if msg==b'':
                i.close()
                read_lista.remove(i)
                continue
            print(msg)
            i.send(b'bye')

        IO多路复用模块 selectors

'''
selectors  会自动选择适合你当前系统的 io多路复用模式
           例如在win上使用select 在linux上是用poll或epoll


'''
from socket import *
import selectors


def accpet(server_fileobj,mask):
    conn,addr=server_fileobj.accpet()
    sel.register(conn,selectors.EVENT_READ,read)

def read(conn,mask):
    try:
        data=conn.recv(1024)
        if not data:
            print('closing',conn)
            sel.unregister(conn)
            conn.close()
            return
        conn.send(data.upper()+b'_SB')
    except Exception:
        print('closing',conn)
        sel.unregister(conn)
        conn.close()
sel=selectors.DefaultSelector()# 选择一个适合我的io多路复用
sk=socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sk.bind(('127.0.0.1',8088))
sk.listen(5)
sk.setblocking(False)
#相当于往select的读列表李append了一个文件句柄server_fileobj
#说白了就是 如果有人请求链接sk就appect方法
sel.register(sk,selectors.EVENT_READ,accpet)

while True:
    events=sel.select()   #检测所有的fileobj 时候有完成wait_data的
    for sel_obj,mask in events:
        callback=sel_obj.data#callback=accpet
        callback(sel_obj.fileobj,mask)#accpet(server_fileobj,1)

  

总结:

         python中的线程模块multiprocess和threading模块用法基本相同,概念相似。但是和协程的不同在于,进程和线程都是系统调度的,代码层面无法介入,但是协程是由代码控制的。

类似于 系统---多进程----多线程----多协程。

猜你喜欢

转载自www.cnblogs.com/yuan-x/p/12205175.html
今日推荐