多线程补充以及协程

多线程补充以及协程

1.线程队列

线程队列用法与进程队列一样

import queue     #先进先出
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())   # 1
print(q.get())   # 2
print(q.get())   # 3
print(q.get(block=False))   # 取不到值直接报错
q.get(timeout=2)   # 阻塞2秒,还没有值直接报错

import queue  # 后进先出 LiFo 堆栈
q = queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put('alex')
print(q.get())   # 'alex'
print(q.get())   # 2
print(q.get())   # 1

import queue   # 优先级队列
q = queue.PriorityQueue
q.put((5,'alex'))
q.put((-2,'七七'))
q.put((0,'赫赫'))
print(q.get())   # (-2,'七七')
print(q.get())   # (0,'赫赫')
print(q.get())   # (5,'alex')
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
#如果两个值的优先级一样,那么按照后面的值的acsii码顺序来排序,如果字符串第一个数元素相同,比较第二个元素的acsii码顺序.优先级相同的两个数据,他们后面的值必须是相同的数据类型才能比较,可以是元组,也是通过元素的ascii码顺序来排序

2.事件

开启两个线程,一个线程运行到中间的某个阶段,触发另个线程执行.两个线程增加了耦合性.

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。
为了解决这些问题,我们需要使用threading库中的Event对象。对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象,而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真.一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行.

event.is_set():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

版本一:
如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作
from threading import Thread,current_thread
import time

flag = False
def check():
    print(f'{current_thread().name} 检测服务器是否开启..')
    time.sleep(3)
    global flag
    flag = True
    print("服务器已经开启..")

def connect():
    while 1:
        print(f'{current_thread().name} 等待连接..')
        time.sleep(0.5)
        if flag:
            print(f'{current_thread().name} 连接成功..')
            break

t1 = Thread(target = check,)
t2 = Thread(target = connect,)
t1.start()
t2.start()

版本二:事件event

from threading import Thread,current_thread,Event
import time

event = Event()
def check():
    print(f'{current_thread().name} 检测服务器是否开启..')
    time.sleep(3)
    #print(event.is_set())  返回event的状态
    event.set()
    #print(event.is_set())
    print('服务器已经开启...')

def connect():
    print(f'{current_thread().name} 等待连接..')
    #event.wait()  #阻塞,直到event.set() 方法之后
    event.wait(1) #只阻塞1秒,1秒后还没进行set,直接进行下一步操作
    print(f'{current_thread().name} 连接成功')

t1 = Thread(target = check,)
t2 = Thread(target = connect,)
t1.start()
t2.start()

小测试:
一个线程检测服务器是否开启,另一个线程判断如果开始了,则显示连接成功,此线程只尝试连接3次,1s一次,超过3次,还没有连接成功,则显示连接失败

from threading import Thread,current_thread,Event
import time

event = Event()
def check():
    print(f'{current_thread().name} 检测服务器是否开启..')
    time.sleep(3)
    event.set()
    print('服务器已经开启..')

def connect():
    count = 1
    while not event.is_set():
        if count == 4:
            print('连接次数过多,已断开..')
            break
        event.wait(1)
        print(f'{current_thread().name} 尝试连接{count}次')
        count += 1
    else:
        print(f'{current_thread().name} 连接成功..')

3.协程

串行:一个线程执行一个任务,执行完毕后执行下一个任务
并行:多个cpu执行多个任务
并发:一个cpu执行多个任务
并发真正的核心:切换并且保留状态

单个cpu并发的执行10个任务:
1.方式一:开启多进程,并发执行,操作系统切换+保持状态
2.方式二:开启多线程,并发执行,操作系统切换+保持状态
3.方式三:开启协程并发的执行,程序把控cpu在任务之间来回的切换+保持状态

什么是协程?
是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

协程的本质就是在单线程下,由用户自己控制一个任务遇到IO阻塞后,切换另一个任务去执行,以此来提高效率.

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

对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度的利用cpu
缺点:
1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程开启多个线程,每个线程内开启协程.

协程的特点:
1.必须在只有一个单线程内实现并发
2.修改共享数据不需加锁
3.用户程序内自己保存多个控制流的上下文栈(保持状态)
4.一个协程遇到IO操作自动切换到其它协程
安装:
    pip3 install greenlet

真正的协程模块就是使用greenlet完成的切换

版本1:切换+保留状态(遇到IO不会主动切换)
from greenlet import greenlet
import time

def eat(name):
    print('%s eat 1' %name)          #2
    g2.switch('taibai')              #3
    #time.sleep(3) 遇到阻塞不会主动切换
    print('%s eat 2' %name)          #6
    g2.switch()                      #7
def play(name):
    print('%s play 1' %name)         #4
    g1.switch()                      #5
    print('%s play 2' %name)         #8

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

g1.switch('taibai')#可以在第一次switch时传入参数,以后都不需要

版本2:协程,模拟的阻塞,不是真正的阻塞

from threading import current_thread
import time,gevent

def eat(name):
    print('%s eat 1' %name)
    print(current_thread().name)
    gevent.sleep(2)  #模拟阻塞
    # time.sleep(2)  #真正阻塞,不会主动切换
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    print(current_thread().name)
    gevent.sleep(1)
    # time.sleep(1)
    print('%s play 2' %name)

g1 = gevent.spawn(eat,'egon')
g2 = gevent.spawn(play,'egon')
print(f'主{current_thread().name}')
g1.join()
g2.join()

版本3:

import gevent,time
from gevent import monkey

monkey.patch_all()  # 打补丁: 将下面的所有的任务的阻塞都打上标记
def eat(name):
    print('%s eat 1' %name)
    time.sleep(2)
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    time.sleep(1)
    print('%s play 2' %name)

g1 = gevent.spawn(eat,'egon')
g2 = gevent.spawn(play,'egon')
gevent.joinall([g1,g2])
gevent介绍
安装:
    pip3 install gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

用法:

g1=gevent.spawn(func,1,2,3,x=4,y=5) #创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的,spawn是异步提交任务

g1.join() #等待g1结束

g2.join() #等待g2结束  有人测试的时候会发现,不写第二个join也能执行g2,是的,协程帮你切换执行了,但是你会发现,如果g2里面的任务执行的时间长,但是不写join的话,就不会执行完等到g2剩下的任务了

? 一般在工作中我们都是进程+线程+协程的方式来实现并发,以达到最好的并发效果,如果是4核的cpu,一般起5个进程,每个进程中20个线程(5倍cpu数量),每个线程可以起500个协程,大规模爬取页面的时候,等待网络延迟的时间的时候,我们就可以用协程去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并发,这是一般一个4cpu的机器最大的并发数。nginx在负载均衡的时候最大承载量就是5w个.

? 单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2...如此,才能提高效率,这就用到了Gevent模块。

原文地址:https://www.cnblogs.com/tutougold/p/11421174.html

时间: 2024-10-09 03:00:31

多线程补充以及协程的相关文章

服务器开发中的多进程,多线程及多协程

服务器开发中,为了充分利用多核甚至多个cpu,或者是简化逻辑编写的难度,会应用多进程(比如一个进程负责一种逻辑)多线程(将不同的用户分配到不同的进程)或者协程(不同的用户分配不同的协程,在需要时切换到其他协程),并且往往同时利用这些技术比如多进程多线程. 一个经典的服务器框架可以说如下的框架: 而这些服务器进程之间协同配合,为用户提供服务,其中中心服务器提供集群的调度工作,而逻辑服可以是逻辑服务器1提供登录服务.逻辑服务器2提供购买服务:也可以是2个服务器提供相同服务,然后视负载用户数将不同用户

Python学习之路-随笔03 多线程/进程和协程(上篇)

最近东西积攒了太多,感觉再不写进来就要炸了. 1.多线程 1.11 关于多线程的包 相关的python包有几个,比如thread包,到py3改成_thread,而thread有一些问题使得不是很好用.通用的包叫threading.最近都是在用这个. 1.12 threading的使用和常用属性 需要注意的点有生成实例比如t = threading.Thread(target=xxx, args=(xx,)),里面有两个参数,第一个是目标函数,第二个是相关的参数,注意类型. 然后就是start启动

谈谈你对多进程,多线程,以及协程的理解,项目是否用??

这个问题被问的概率相当之大,其实多线程,多进程,在实际开发中用到的很少,除非是那些对项目性能要求特别高的,有的开发工作几年了,也确实没用过,你可以这么回答,给他扯扯什么是进程,线程(cpython中是伪多线程)的概念就行,实在不行你就说你之前写过下载文件时,用过多线程技术,或者业余时间用过多线程写爬虫,提升效率. 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大. 线程: 调度执行的最小单位,也

python多线程、多进程、协程的使用

本文主要介绍多线程.多进程.协程的最常见使用,每个的详细说明与介绍有时间会在以后的随笔中体现. 一.多线程 1.python通过两个标准库thread和threading提供对线程的支持.thread提供了低级别的.原始的线程以及一个简单的锁.threading通过对thread模块进行二次封装,提供了更方便的API来操作线程.接下来只介绍threading的常见用法. 2.使用 import threading import time def Traversal_5(interval): fo

python并发之多进程、多线程、协程和异步

一.多线程 二.协程(又称微线程,纤程) 协程,与线程的抢占式调度不同,它是协作式调度.协程在python中可以由generator来实现. 首先要对生成器和yield有一个扎实的理解. 调用一个普通的python函数,一般是从函数的第一行代码开始执行,结束于return语句.异常或者函数执行(也可以认为是隐式地返回了None). 一旦函数将控制权交还给调用者,就意味着全部结束.而有时可以创建能产生一个序列的函数,来“保存自己的工作”,这就是生成器(使用了yield关键字的函数). 能够“产生一

关于协程:nodejs和golang协程的不同

nodejs和golang都是支持协程的,从表现上来看,nodejs对于协程的支持在于async/await,golang对协程的支持在于goroutine.关于协程的话题,简单来说,可以看作是非抢占式的轻量级线程. 协程本身 一句话概括,上面提到了 "可以看作是非抢占式的轻量级线程". 在多线程中,把一段代码放在一个线程中执行,cpu会自动将代码分成碎片,并在一定时间切换cpu控制权,线程通过锁机制确保自己使用的资源在cpu执行别的线程的代码时被修改(占用的内存堆栈.硬盘数据资源等)

Python快速学习第十二天--生成器和协程

yield指令,可以暂停一个函数并返回中间结果.使用该指令的函数将保存执行环境,并且在必要时恢复. 生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯通. 看下面一段代码: [python] view plain copy def gen(): for x in xrange(4): tmp = yield x if tmp == 'hello': print 'world' else: print str(tmp) 只要函数中包含yield关键字,该函数调用就是生成器对象. [pytho

Python开发【第九篇】:协程、异步IO

协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是协程,协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈.因此,协程能保留上一次调用的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法,进入上一次离开时所处逻辑流的位置. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返

什么是协程?

协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕. 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序. 子程序调用总是一个入口,一次返回,调用顺序是明确的.而协程的调用和子程序不同. 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当