dawn-liu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。

一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

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

缺点:

#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

协程特点:

必须在只有一个单线程里实现并发
修改共享数据不需加锁
用户程序里自己保存多个控制流的上下文栈
附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

 

greenlet模块

属于第三方模块需要安装,pip3 install greenlet

# from greenlet import greenlet
# import time
# def eat(name):
#     print('%s eat 1'%name)
#     time.sleep(100) #遇到阻塞不会切换
#     g2.switch('egon')
#     print('%s eat 2' % name)
#     g2.switch()
#
#
# def play(name):
#     print('%s play 1'%name)
#     g1.switch()
#     print('%s play 2' % name)
#
# g1=greenlet(eat)
# g2=greenlet(play)
#
# g1.switch('egon')

gevent模块

gevent模块本质用的是greenlet,比他多了个i/o监测功能

import gevent,time
def eat(name):
    print('%s eat 1'%name)
    gevent.sleep(3) #此时是不识别time.sleep的
    print('%s eat 2' % name)



def play(name):
    print('%s play 1'%name)
    gevent.sleep(2)
    print('%s play 2' % name)
start=time.time()
g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,'alex')
# gevent.sleep(10)
# g1.join()
# g2.join()

gevent.joinall([g1,g2]) 
print(time.time()-start) #3.0121257305145264

让它识别time等指令

from gevent import monkey;monkey.patch_all() #打补丁,实现gevent模块识别time等i/0,而切换,必须顶格写

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

def play(name):
    print('%s play 1'%name)
    time.sleep(2)
    print('%s play 2' % name)
start=time.time()
g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,'alex')

gevent.joinall([g1,g2]) 
print(time.time()-start)

 其他注意:使用pycharm运行gevent的时候可能会出现一个回环错误,这个时候你要注意查看

run --->edit configurations...  --->去掉前面的勾 Run with python console

 

通过gevent实现单线程下的socket并发

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

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def server(server_ip,port):
    s=socket(AF_INET,SOCK_STREAM)
    s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    s.bind((server_ip,port))
    s.listen(5)
    while True:
        conn,addr=s.accept()
        gevent.spawn(talk,conn,addr)

def talk(conn,addr):
    try:
        while True:
            res=conn.recv(1024)
            print('client %s:%s msg: %s' %(addr[0],addr[1],res))
            conn.send(res.upper())
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    server('127.0.0.1',8080)
服务端
from socket import *

client=socket(AF_INET,SOCK_STREAM)
client.connect(('127.0.0.1',8080))


while True:
    msg=input('>>: ').strip()
    if not msg:continue

    client.send(msg.encode('utf-8'))
    msg=client.recv(1024)
    print(msg.decode('utf-8'))
客户端
from threading import Thread
from socket import *
import threading

def client(server_ip,port):
    c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了
    c.connect((server_ip,port))

    count=0
    while True:
        c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))
        msg=c.recv(1024)
        print(msg.decode('utf-8'))
        count+=1
if __name__ == '__main__':
    for i in range(500):
        t=Thread(target=client,args=('127.0.0.1',8080))
        t.start()
多线程并发多个客户端
posted on 2018-08-10 11:06  dawn-liu  阅读(288)  评论(0编辑  收藏  举报