我的python之路【第九章】线程.进程.协程

 

本节大纲

day9
    - 线程进程介绍
        1. 工作最小单元是线程
        2. 应用程序 -> 至少有一个进程  -> 至少有一个线程
        3. 应用场景:
             IO密集型:线程
            计算密集型:进程
        4. GIL,全局解释器锁。
            - 保证同一个进程中只有一个线程同时被调度
    - 线程
        1. 基本使用
            def task(arg):
                time.sleep(arg)
                print(arg)

            for i in range(5):
                t = threading.Thread(target=task,args=[i,])
                # t.setDaemon(True) # 主线程终止,不等待子线程
                # t.setDaemon(False)
                t.start()
                # t.join()  # 一直等
                # t.join(1) # 等待最大时间
        2. 锁
            # 1. 只能有一个人使用锁
                # lock = threading.Lock() # 只能开一把
                # lock = threading.RLock()# 可以开多把
            # 2. 多个人同时使用锁
                # lock = threading.BoundedSemaphore(3)
            # 3. 所有的解脱锁的限制
                # lock = threading.Event()
            # 4. 肆意妄为
                # lock = threading.Condition()
        
        3. 线程池
            模式一:直接处理
                def task(url):
                    """
                    任务执行两个操作:下载;保存本地
                    """
                    # response中封装了Http请求响应的所有数据
                    # - response.url            请求的URL
                    # - response.status_code    响应状态码
                    # - response.text           响应内容(字符串格式)
                    # - response.content        响应内容(字节格式)
                    # 下载
                    response = requests.get(url)
                    
                    # 下载内容保存至本地
                    f = open('a.log','wb')
                    f.write(response.content)
                    f.close()

                pool = ThreadPoolExecutor(2)
                url_list = [
                    'http://www.oldboyedu.com',
                    'http://www.autohome.com.cn',
                    'http://www.baidu.com',
                ]
                for url in url_list:
                    print('开始请求',url)
                    # 去连接池中获取链接
                    pool.submit(task,url)
            
            
            模式二:分步处理
                def save(future):
                    """
                    只做保存  # future中包含response
                    """
                    response = future.result()
                    
                    # 下载内容保存至本地
                    f = open('a.log','wb')
                    f.write(response.content)
                    f.close()
                
                def task(url):
                    """
                    只做下载 requests
                    """
                    # response中封装了Http请求响应的所有数据
                    # - response.url            请求的URL
                    # - response.status_code    响应状态码
                    # - response.text           响应内容(字符串格式)
                    # - response.content        响应内容(字节格式)
                    # 下载
                    response = requests.get(url)
                    return response

                pool = ThreadPoolExecutor(2)
                url_list = [
                    'http://www.oldboyedu.com',
                    'http://www.autohome.com.cn',
                    'http://www.baidu.com',
                ]
                for url in url_list:
                    print('开始请求',url)
                    # 去连接池中获取链接
                    # future中包含response
                    future = pool.submit(task,url)
                    # 下载成功后,自动调用save方法
                    future.add_done_callback(save)
                    
    - 进程
        1. 基本使用
            from multiprocessing import Process
            import time
            def task(arg):
                time.sleep(arg)
                print(arg)

            if __name__ == '__main__':
                for i in range(10):
                    p = Process(target=task,args=(i,))
                    p.daemon = True
                    # p.daemon = False
                    p.start()
                    p.join(1)

                print('主进程最后...')
        
        2. 进程之间的数据共享
             特殊的东西
             - Array(‘类型’,长度)
             - Manager().list() / Manager().dict()
        
        3. 进程池
        
        
        ================== 结论 ==================
        IO密集:线程
        计算密集:进程
        
                    
        
    - 协程 
            pip3 install greenlet
            协程永远是一个线程在执行,对线程的一个分片处理。
        
        二次加工:
            自定义:
                select实现
            现成 :
                pip3 install gevent
    
    - IO多路复用
        监听多个socket对象是否有变化(可读,可写,发送错误)
        - 示例一:

    - socketserverIO
         - IO多路复用
         - 线程
    - 自定义异步非阻塞的框架
    
    
    
    
参考博客:
    线程、进程、协程:
        http://www.cnblogs.com/wupeiqi/articles/5040827.html
    IO多路复用:
        http://www.cnblogs.com/wupeiqi/articles/5040823.html
    
    
本周作业:
    服务端:socketserver
        用IO多路复用select,使用“伪”并发
   
            
    客户端:
        基本操作:
            聊天
            上传

    尝试:
        客户端是否可以用select来实现???
    
   
    
View Code

 

1.线程的基本使用

import threading
import time

def task(arg):
    time.sleep(arg)
    print(arg)
#target 写函数名,args 传入参数,必须是可迭代的
# t=threading.Thread(target=task,args=[13,])
# t.start()       #start 是说准备好了,等待cpu来执行
for i in range(30):
    t=threading.Thread(target=task,args=[i,])
    t.setDaemon(True)   #主线程终止,不等待子线程----只能在start之前定义
    t.start()       #start 是说准备好了,等待cpu来执行
    # t.join()        #一直等待子线程执行完成后,执行主线程
    # t.join(1)       #等待最大时间,1s 如果子线程没有执行完就继续执行,每次等待1s
print('end')

2.继承threading.Thread 重构父类方法

#继承父类threading.Thread
class MyThread(threading.Thread):
    def __init__(self,func,*args,**kwargs): #重构父类的构造方法
        super(MyThread,self).__init__(*args,**kwargs)   #继承父类的构造方法
        self.func=func  #加入一个参数
    def run(self):  #重构run 方法
        print('子类')
        self.func()

def test():
    time.sleep(1)
    print(1111)
obj=MyThread(func=test)
#这里的start方法其实还是从父类继承过来的。他其实是去调用了self.run() 方法,由于子类重构了run方法
obj.start() #执行了子类中的run() 方法

线程锁:

import threading
import time
v=10
#1.只能有一个人使用锁
# lock=threading.Lock()   #创建一个锁,只能进去一个线程
# # lock=threading.RLock()  #支持可以上两把锁
# def task(arg):
#     time.sleep(2)
#     #申请使用锁,其它人等待
#     print('启动线程:',arg)
#     lock.acquire()
#     # lock.acquire()     #配合Rlock使用
#     time.sleep(1)
#     global v
#     v-=1
#     print(v)
#     #释放锁
#     lock.release()
#     # lock.release()    #配合Rlock使用
# for i in range(10):
#     t=threading.Thread(target=task,args=(i,))
#     t.start()
'''2.多人同时使用锁'''
#-----规定同时可以有三人使用锁-----
# lock=threading.BoundedSemaphore(3)
# def task(arg):
#     time.sleep(2)
#     #申请使用锁,其它人等待
#     print('启动线程:',arg)
#     lock.acquire()
#     time.sleep(1)
#     global v
#     v-=1
#     print(v)
#     #释放锁
#     lock.release()
#     # lock.release()    #配合Rlock使用
# for i in range(10):
#     t=threading.Thread(target=task,args=(i,))
#     t.start()
'''3.所有的线程解脱锁的限制'''
# #事件锁
# lock=threading.Event()
# def task(arg):
#     time.sleep(1)
#     #申请使用锁,其它人等待
#     print('启动线程:',arg)
#     #锁住所有的线程,全部暂停.等待释放-----
#     lock.wait()
#     print(v)
#
# for i in range(10):
#     t=threading.Thread(target=task,args=(i,))
#     t.start()
# while True:
#     value=input('>>')
#     if value=='1':
#         #local.set() 放开所有被锁住的线程
#         lock.set()  #其实就将wait中的_flag 标签修改成True。并执行_cond.notify_all()
#         #与set()相对的
#         # lock.clear() # self._flag = False 但不执行其它操作
'''4.肆意妄为'''
lock=threading.Condition()      #自定义锁中允许通过的数量,可以实时的传参
#配合地址池使用
def task(arg):
    time.sleep(2)
    #申请使用锁,其它人等待
    print('启动线程:',arg)
    lock.acquire()
    lock.wait()
    print('线程,',arg)
    lock.release()
    # lock.release()    #配合Rlock使用
for i in range(10):
    t=threading.Thread(target=task,args=(i,))
    t.start()
while True:
    value=input('>>>')  #输入要运行线程的数量
    lock.acquire()
    lock.notify(int(value))     #自定义通过锁的线程数量
    lock.release()

线程池:

'''创建线程池-基本使用'''
from concurrent.futures import ThreadPoolExecutor
import time
def task(arg):
    time.sleep(0.5)
    print(arg)

pool = ThreadPoolExecutor(5)    #线程池限制最多可以创建5个线程同时执行

for i in range(100):
    #去连接池中获取连接 -- 去执行
    pool.submit(task,i) #
#程序执行的过程发现,一次最多出现5个线程执行的结果
# 线程池,等待
from concurrent.futures import ThreadPoolExecutor, wait
#存放线程任务
fs = []
# 循环端口进行socket探测
for port in range(int(startport), int(endport)):
# 将启动的线程任务存放到fs
fs.append(self.pool.submit(self.socket_connect, ip, port))
# 等待fs中所有的线程task完成,如果是等待时间就,可以通过finished和doing 获取当前完成多少个线程任务,正在运行的有多少线程任务。len(doing)
finished, doing = wait(fs,)
self.log.info("-----------批量扫描端口结束-------------")

使用线程池请求http

#C:\Users\liuhao\AppData\Local\Programs\Python\Python35\Scripts
#pip3 install requests
'''使用线程池请求http '''
from concurrent.futures import ThreadPoolExecutor
import requests
def task(url):
    response = requests.get(url)
    print('得到结果:',url,len(response.content))
pool = ThreadPoolExecutor(2)    #线程池中创建两个线程--- 他会等待 线程执行完毕再执行下一个线程
url_list=[
    'http://www.baidu.com',
    'http://www.taobao.com',
    'http://www.jingdong.com'
]
for url in url_list:
    print('开始请求',url)
    #去连接池中获取连接 --去执行
    pool.submit(task,url)

回调函数

'''利用回调函数,实现代码的多种功能的扩展性'''
from concurrent.futures import ThreadPoolExecutor
import requests
def txt(future):    #根据add_done_callbak 回调函数 执行下列操作
    download_response=future.result()   #获取download函数返回的结果
    print('处理中',download_response.url,download_response.status_code,len(download_response.text))

def download(url):
    response=requests.get(url)
    return response #response 包含里下载的所有内容

pool = ThreadPoolExecutor(2)
url_list = [
    'http://www.jingdong.com',
    'http://www.baidu.com',
    'http://www.taobao.com',
]
for url in url_list:
    #去连接池中获取连接
    print('开始请求',url)
    future = pool.submit(download,url)  #执行这个方法返回一个实例
    future.add_done_callback(txt)   #通过这个返回的实例,调用传入的方法   也可以传入一个其它的方法-比如打印网站信息等等-做一些爬虫的匹配

在线程池中设置等待程序执行完毕(wait)

wait方法接会返回一个tuple(元组),tuple中包含两个set(集合),一个是completed(已完成的)另外一个是uncompleted(未完成的)。

使用wait方法的一个优势就是获得更大的自由度,它接收三个参数

  FIRST_COMPLETED

  FIRST_EXCEPTION

  ALL_COMPLETE,默认设置为ALL_COMPLETED

默认: wait(fs, timeout=None, return_when=ALL_COMPLETED)
# 首先要在导入线程池的同时,导入wait
from concurrent.futures import ThreadPoolExecutor,wait
# 定义线程池
futures = []
pool = ThreadPoolExecutor(10)
for num in range(10):
    futures.append(pool.submit(function, num))
# 如果采用默认的ALL_COMPLETED,程序会阻塞直到线程池里面的所有任务都完成。
wait(futures)
# 如果采用FIRST_COMPLETED参数,程序并不会等到线程池里面所有的任务都完成。
#print(wait(futures,timeout=None,return_when='FIRST_COMPLETED'))

参考博文:http://python.jobbole.com/87272/

进程的基本使用--------就使用而言-只需要将调用的Thread改成Process就可以了-----其它的功能可线程都一样

from multiprocessing import Process
import time

def task(arg):
    time.sleep(arg)
    print(arg)

if __name__ == '__main__':#windows中必须这样写,才能执行
    for i in range(10):
        p=Process(target=task,args=(i,))
        p.daemon=True   #主进程不等待子进程执行完毕,
        # p.daemon = False    #默认是False
        p.start()
        p.join(1)
    print('主进程中的主线程执行到最后......')

演示:进程之间的数据不共享------但是线程之间的数据是共享的

#验证进程之间数据不共享
from multiprocessing import Process
from threading import Thread
def task(num,li):
    li.append(num)
    print(li)
if __name__ == '__main__':
    v=[]
    for i in range(10):
        # p = Thread(target=task, args=(i, v,)) #演示线程的数据共享
        p=Process(target=task,args=(i,v,))  #演示进程的数据不共享
        p.start()

进程之间如果想要实现数据共享---需要特殊的数据类型

# 方式一:
#进程之间通过特殊的数据类型进行数据共享----C语言中的Array 数组
from multiprocessing import Process,Array
def task(num,li):
    li[num]=1
    print('进程:',num,list(li))
if __name__ == '__main__':
    # v=Array('数据类型','长度')
    v=Array('i',20)   #C语言中数组,
    for i in range(20):
        p=Process(target=task,args=(i,v,))
        p.start()

利用Manager实现进程之间的数据共享

#使用Manager 实现进程之间的数据共享:提倡使用
from multiprocessing import Process,Manager
def task(num,li):
    li.append(num)
    print('进程:',num,li)
if __name__ == '__main__':
    # dic=Manager().dict()
    v=Manager().list()

    for i in range(10):
        p=Process(target=task,args=(i,v,))
        p.start()
        #会有报错-原因:进程之间建立socket连接进行通讯,
        #主进程关闭,socket连接意外断开---
        # p.join()    #两种模拟解决方式---提倡用进程池
    input('>>>')

进程池的调用:

'''进程池--方式一:直接调用'''
from concurrent.futures import ProcessPoolExecutor
import time
def task(arg):
    time.sleep(1)
    print(arg)
if __name__ == '__main__':
    pool= ProcessPoolExecutor(2)
    for i in range(10):
        pool.submit(task,i)
#方式二: 分布进行
from concurrent.futures import ProcessPoolExecutor
import time
def call(arg):
    data=arg.result()
    time.sleep(1)
    print(data)
def task(arg):  #将arg+100 返回结果
    print(arg)
    return arg+100
if __name__ == '__main__':
    pool= ProcessPoolExecutor(2)
    for i in range(10):
        obj=pool.submit(task,i) #obj获取到了task返回的结果,但是不能直接调用,需要特殊的方法才能获取
        obj.add_done_callback(call) #回调函数执行call

协程:

Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

#协程:使用一个线程去执行多个任务,将一个线程的时间进行切片-- 实现单线程的多并发
'''建议使用debug模式去查看下面代码的运行流程'''
from greenlet import greenlet       #导入协程的模块
def f1():
    print('A+')
    g2.switch()         #跳转到g2 对象
    print('B+')
    g2.switch()
def f2():
    print('A-')
    g1.switch()         #跳转到g1对象
    print('B-')
    g1.switch()
g1= greenlet(f1)    #根据协程 创建g1对象,传入函数f1
g2= greenlet(f2)    #根据协程 创建g2对象,传入函数f2
g1.switch()         #开始运行

 

# 根据协程二次开发:协程+IO
from gevent import monkey;monkey.patch_all()    #下面是协程配合IO操作进行的伪并发
import gevent
import requests
def f(url):
    response=requests.get(url)
    print(response.url,response.status_code)
#进行执行
gevent.joinall([
    gevent.spawn(f,'http://www.baidu.com/'),
    gevent.spawn(f,'http://www.taobao.com/'),
    gevent.spawn(f,'http://www.jingdong.com/'),
])

 

IO多路复用:server端

# IO多路复用:8002,8001
'''利用IO多路复用:实现单线程多并发的server端'''
import socket,select
sk1 = socket.socket()
sk1.bind(('127.0.0.1',8001,))
sk1.listen(5)

sk2 = socket.socket()
sk2.bind(('127.0.0.1',8002,))
sk2.listen(5)
inputs = [sk1,sk2,]
while True:
    # IO多路复用,同时监听多个socket对象
    #    - select,内部进行循环操作(1024)  主动查看
    #    - poll, 内部进行循环操作         主动查看
    #    - epoll,                        被动告知
    r,w,e = select.select(inputs,[],[],0.05)
    # r = [sk2,]        #r中只有请求来的时候才会有值,没有请求过来-就没有值
    # r = [sk1,]
    # r = [sk1,sk2]
    # r = []
    # r = [conn,]
    # r = [sk1,Wconn]

    for obj in r:
        if obj in [sk1,sk2]:
            # 新连接捡来了...
            print('新连接来了:',obj)
            conn,addr = obj.accept()
            inputs.append(conn)     #将conn连接加入到进行监听的列表中
        else:
            # 有连接用户发送消息来了..
            print('有用户发送数据了:',obj)
            data = obj.recv(1024)
            obj.sendall(data)

clinet:

'''测试使用的客户端'''
import socket
client = socket.socket()
client.connect(('127.0.0.1',8002,))
while True:
    v = input('>>>')
    client.sendall(bytes(v,encoding='utf-8'))
    ret = client.recv(1024)
    print('服务器返回:',ret)

IO多路复用高级版

1.解决客户端断开连接服务端异常退出问题

2.实现将用户请求分离

import socket
import select

# IO多路复用:8002,8001
#
############################### 基于select实现服务端的“伪”并发 ###############################
sk1 = socket.socket()
sk1.bind(('127.0.0.1',8001,))
sk1.listen(5)

sk2 = socket.socket()
sk2.bind(('127.0.0.1',8002,))
sk2.listen(5)
inputs = [sk1,sk2,]
w_inputs = []
while True:
    #实现读写 分离, 将读取和写入的操作都分开
    r,w,e = select.select(inputs,w_inputs,inputs,0.05)  #(inputs=读取,w_inputs=写入,inputs=异常)
    for obj in r:
        if obj in [sk1,sk2]:
            # 新连接捡来了...
            print('新连接来了:',obj)
            conn,addr = obj.accept()
            inputs.append(conn)
        else:
            # 有连接用户发送消息来了..
            print('有用户发送数据了:',obj)
            try:
                data = obj.recv(1024)
            except Exception as ex:     #当客户端端口连接,默认会发送数据-----服务端会报错
                data = ""
            if data:    #如果data 有数据 : 将 这个实例加入的写操作中
                w_inputs.append(obj)
                # obj.sendall(data)
                print('有数据:',w_inputs)
            else:   #如果发来的没有数据
                obj.close() #关闭这个实例
                print('没有数据',inputs)
                inputs.remove(obj)  #将inputs中的实例移除
                print('没有数据w:',w_inputs)

    for obj in w:
        obj.sendall(b'ok')
        w_inputs.remove(obj)        #将w_inputs中的实例移除

 

posted @ 2017-03-22 10:25  saynobody  阅读(301)  评论(0编辑  收藏  举报