【Python之路】第九篇--Python基础之线程、进程和协程

进程与线程的历史

  进程就是一个程序在一个数据集上的一次动态执行过程。 进程一般由程序、数据集、进程控制块三部分组成。

  我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

  在早期的操作系统里,计算机只有一个核心,进程执行程序的最小单位,任务调度采用时间片轮转的抢占式方式进行进程调度。每个进程都有各自的一块独立的内存,保证进程彼此间的内存地址空间的隔离。 随着计算机技术的发展,进程出现了很多弊端,一是进程的创建、撤销和切换的开销比较大,二是由于对称多处理机(对称多处理机(SymmetricalMulti-Processing)又叫SMP,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构)的出现,可以满足多个运行单位,而多进程并行开销过大。

  这个时候就引入了线程的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。 线程没有自己的系统资源,只拥有在运行时必不可少的资源。但线程可以与同属与同一进程的其他线程共享进程所拥有的其他资源。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

进程与线程之间的关系

  线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。

  线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

Python GIL(Global Interpreter Lock) 

  In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) 

  上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

  首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

  这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

 

threading模块

  Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。       ?中文文档

1. 直接调用:

import threading
import time

def worker(num):
    time.sleep(1)
    print("Thread %d"%num)
    return

for i  in range(10):
    t = threading.Thread(target=worker,args=(i,),name="t.%d"%i)
    t.start()

print('main thread stop')

上述代码创建了10个“前台”线程,然后控制器就交给了CPU,CPU根据指定算法进行调度,分片执行指令。

更多方法:

t.start() : 激活线程,

t.getName() : 获取线程的名称

t.setName() : 设置线程的名称 

t.name : 获取或设置线程的名称

t.is_alive() : 判断线程是否为激活状态

t.isAlive() :判断线程是否为激活状态

t.setDaemon()  设置为后台线程(True)或前台线程(False)(默认为:False);

        通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之前才可以使用。

        如果是守护线程,主线程执行过程中,守护线程也在进行,主线程执行完毕后,守护线程不论成功与否,均停止;

        如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序才停止

t.isDaemon() : 判断是否为守护线程

t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。

t.join() :等待直至线程终止。这将阻塞调用线程,直到调用join()方法的线程终止(通常或通过未处理的异常),或直到可选超时发生。

t.run() :线程被cpu调度后自动执行线程对象的run方法

2.继承式调用:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

import threading,time

class MyThread(threading.Thread):
    def __init__(self, num):
        threading.Thread.__init__(self)
        self.num = num

    def run(self):
        print("Threading Running ... %s"%self.num)
        time.sleep(1)


if __name__ == '__main__':
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()
View Code

注意: 程序退出前,会确保全部线程都执行完成 , 再退出。 , 所以默认末尾有个join()

线程锁(Lock、RLock)

  一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据

  由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

import threading
import time
 
globals_num = 0
 
lock = threading.RLock()
 
def Func():
    lock.acquire()  # 获得锁 
    global globals_num
    globals_num += 1
    time.sleep(1)
    print(globals_num)
    lock.release()  # 释放锁 
 
for i  in range(10):
    t = threading.Thread(target=Func)
    t.start()

threading.RLock和threading.Lock 的区别

  RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。

  如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

import threading
lock = threading.Lock()    #Lock对象
lock.acquire()
lock.acquire()  #产生了死琐。
lock.release()
lock.release()
import threading
rLock = threading.RLock()  #RLock对象
rLock.acquire()
rLock.acquire()    #在同一线程内,程序不会堵塞。
rLock.release()
rLock.release()

GIL VS Lock 

既然Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下就明白了。

【Python之路】第九篇--Python基础之线程、进程和协程_第1张图片

  既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?

  加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了。

  为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,  这可以说是Python早期版本的遗留问题。

信号量(Semaphore)

  互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading,time
 
def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s" %n)
    semaphore.release()
 
if __name__ == '__main__':
 
    semaphore  = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()

事件 ( threading.Event )

  Event是线程间通信最间的机制之一:一个线程发送一个event信号,其他的线程则等待这个信号。用于主线程控制其他线程的执行。

  Events 管理一个flag,这个flag可以使用set()设置成True或者使用clear()重置为False,wait()则用于阻塞,在flag为True之前。flag默认为False。

  • Event.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。

  • Event.set() :将标识位设为Ture

  • Event.clear() : 将标识伴设为False。

  • Event.isSet() :判断标识位是否为Ture。

import threading

def do(event):
    print('start')
    event.wait()
    print('execute')

if __name__ == '__main__':

    event_obj = threading.Event()
    for i in range(10):
        t = threading.Thread(target=do, args=(event_obj,))
        t.start()

    event_obj.clear()
    inp = input('input:')
    if inp == 'true':
        event_obj.set()

  当线程执行的时候,如果flag为False,则线程会阻塞,当flag为True的时候,线程不会阻塞。它提供了本地和远程的并发性。

#!/usr/bin/env python
# -*-coding:utf-8 -*-

import threading,time
import random

def light():
    count = 1
    while True:
        if count > 6 and count < 10 :
            time.sleep(1)
            event.clear()
            print(" Red Right On .... ")
        elif count > 10:
            count = 0
        else:
            time.sleep(1)
            event.set()
            print(" Green Right On .... ")
        count += 1

def car(num):
    while True:
        time.sleep(random.randrange(5))
        if event.is_set():
            print('Car [%s] is across the Street ..'%num)
        else:
            print('Car [%s] is Waitting the Right ..'%num)

if __name__ == '__main__':

    event = threading.Event()
    event.set()
    Light = threading.Thread(target=light)
    Light.start()

for i in range(3):
    Car = threading.Thread(target=car,args=(i,))
    Car.start()
模拟 : 红绿灯

条件(Condition)

  使得线程等待,只有满足某条件时,才释放n个线程

  使用Condition对象可以在某些事件触发或者达到特定的条件后才处理数据,

  Condition除了具有Lock对象的acquire方法和release方法外,还有wait方法、notify方法、notifyAll方法等用于条件处理。

  threading.Condition([lock]):创建一个condition,支持从外界引用一个Lock对象(适用于多个condtion共用一个Lock的情况),默认是创建一个新的Lock对象。

  acquire()/release():获得/释放 Lock

  wait([timeout]): 线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。调用wait()会释放Lock,直至该线程被Notify()、NotifyAll()或者超时线程又重新获得Lock.

  wait_for(predicate, timeout=None) 等待条件的计算结果为True。predicate应为可调用,其结果将被解释为布尔值。可以提供超时,给出最大等待时间。此程序方法可重复调用wait(),直到满足predicate,或直到发生超时。返回值是谓词的最后一个返回值,如果方法超时,则计算为False。

  notify(n=1): 通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

  notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程(这个一般用得少)

例子1:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

import threading,time

L = []


class boy(threading.Thread):
    def __init__(self, cond, name='A boy'):
        threading.Thread.__init__(self)
        self.cond = cond
        self.name = name

    def run(self):
        time.sleep(1)
        '''''boy start conversation, make sure
           the girl thread stared before send notify'''
        self.cond.acquire()
        print(self.name + ':Hello pretty~,I miss you\n')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ':like moth missing fire\n')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ':and I bought a gift for you in the list L\n')
        L.append('channel5')
        self.cond.notify()
        self.cond.release()


class girl(threading.Thread):
    def __init__(self, cond, name='A girl'):
        threading.Thread.__init__(self)
        self.cond = cond
        self.name = name

    def run(self):
        self.cond.acquire()
        self.cond.wait()
        print(self.name + ':Really, show me how much~\n')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ':you\'re so sweet~')
        self.cond.notify()
        self.cond.wait()
        print(self.name + ':wow~~, that\'s ' + L.pop() + '---the one I dreamed for so long, I love you')
        self.cond.release()


if __name__ == '__main__':
    cond = threading.Condition()
    husband = boy(cond, 'Aidan')
    wife = girl(cond, 'PPP')
    husband.start()
    wife.start()
    # husband.start()
    husband.join()  # wait untill these two threads end
    wife.join()
    print('end converdarion\n')
demo

例子2:

import threading
  
def run(n):
    con.acquire()
    con.wait()
    print("run the thread: %s" %n)
    con.release()
  
if __name__ == '__main__':
  
    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()
  
    while True:
        inp = input('>>>')
        if inp == 'q':
            break
        con.acquire()
        con.notify(int(inp))
        con.release() 

例子3:

def condition_func():

    ret = False
    inp = input('>>>')
    if inp == '1':
        ret = True

    return ret


def run(n):
    con.acquire()
    con.wait_for(condition_func)
    print("run the thread: %s" %n)
    con.release()

if __name__ == '__main__':

    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()
demo

Timer

  定时器,指定n秒后执行某操作

from threading import Timer
 
def hello():
    print("hello, world")
 
t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

queue模块

  Queue 就是队列,它是线程安全。优势在于:实现了解耦 和 提高了处理效率                  ?中文文档

  Queue模块:实现了三类队列,主要差别在于取得数据的顺序上。

  FIFO(First In First Out,先进先出)队列中,最早加入的任务会被最先得到。

  LIFO(Last In First Out,后进先出)队列中,最后加入的任务会被最先得到(就像栈一样)。

  在优先队列中,任务被保持有序(使用heapq模块),拥有最小值的任务(优先级最高)被最先得到。

  举例来说,我们去肯德基吃饭。厨房是给我们做饭的地方,前台负责把厨房做好的饭卖给顾客,顾客则去前台领取做好的饭。这里的前台就相当于我们的队列。

  这个模型也叫生产者-消费者模型。

import queue

q = queue.Queue(maxsize=0)  # 构造一个先进先出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。

q = queue.LifoQueue(maxsize=0)  # 构造一个后进先出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。

q.join()    # 等到队列为空的时候,在执行别的操作
q.qsize()   # 返回队列的大小 (不可靠)
q.empty()   # 当队列为空的时候,返回True 否则返回False (不可靠)
q.full()    # 当队列满的时候,返回True,否则返回False (不可靠)
q.put(item, block=True, timeout=None) #  将item放入Queue尾部,item必须存在,可以参数block默认为True,表示当队列满时,会等待队列给出可用位置,
                         为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。 可选参数timeout,表示 会阻塞设置的时间,过后,
                          如果队列无法给出放入item的位置,则引发 queue.Full 异常
q.get(block=True, timeout=None) #   移除并返回队列头部的一个值,可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,
                      若此时队列为空,则引发 queue.Empty异常。 可选参数timeout,表示会阻塞设置的时候,过后,如果队列为空,则引发Empty异常。
q.put_nowait(item) #   等效于 put(item,block=False)
q.get_nowait() #    等效于 get(item,block=False)

  生产者--消费者:

  在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

  什么是生产者消费者模式

  生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

import queue,threading,time

q = queue.Queue(10)

def Producer(i):
    while True:
        if q.qsize() <= 0:
            print('Produce %s'%(i))
            q.put(i)
            time.sleep(1)

def Consumer(i):
    while True:
        if q.qsize() >= 0 :
            msg = q.get()
            print('Counsumer [%s] get %s'%(i,msg))
            time.sleep(1)

for i in range(4):
    t = threading.Thread(target=Producer, args=(i,))
    t.start()

for i in range(7):
    t = threading.Thread(target=Consumer, args=(i,))
    t.start()

例子2:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

import threading
import queue

def producer():
    for i in range(10):
        q.put("骨头 %s" % i)

    print("开始等待所有的骨头被取走...")
    q.join()
    print("所有的骨头被取完了...")


def consumer(n):
    while q.qsize() > 0:
        print("%s 取到" % n, q.get())
        q.task_done()  # 告知这个任务执行完了


q = queue.Queue()

p = threading.Thread(target=producer, )
p.start()

c1 = consumer("李闯")
View Code

例子3:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

import time,random
import queue,threading

q = queue.Queue()

def Producer(name):
    count = 0
    while count <20:
        time.sleep(random.randrange(3))
        q.put(count)
        print('Producer %s has produced %s baozi..' %(name, count))
        count +=1

def Consumer(name):
    count = 0
    while count <20:
        time.sleep(random.randrange(4))
        if not q.empty():
            data = q.get()
            print(data)
            print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
        else:
            print("-----no baozi anymore----")
        count +=1

p1 = threading.Thread(target=Producer, args=('A',))
c1 = threading.Thread(target=Consumer, args=('B',))
p1.start()
c1.start()
View Code

 

Python 进程

  multiprocessing是python的多进程管理包,和threading.Thread类似。直接从侧面用subprocesses替换线程使用GIL的方式,

  由于这一点,multiprocessing模块可以让程序员在给定的机器上充分的利用CPU。

  在multiprocessing中,通过创建Process对象生成进程,然后调用它的start()方法,但是,由于它是基于fork机制的,因此不被windows平台支持。

  想要在windows中运行,必须使用 if __name__ == '__main__: 的方式

from multiprocessing  import Process
import threading
import time
  
def foo(i):
    print('say hi',i)
  
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

  每一个子进程都是由父进程建立,并且内存空间独立,数据不共享。

from multiprocessing  import Process
import os
import time

def run(i):
    print('Process pid %s , Parent pid %s'%(os.getpid(),os.getppid()))
    time.sleep(1)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=run, args=(i,))
        p.start()
    print('Main Process Done ...' , os.getpid())

进程间数据共享

  进程各自持有一份数据,默认无法共享数据

from multiprocessing  import Process
import os
import time

li = []
p_list = []

def run(i):
    li.append(os.getpid())
    print(os.getpid(),li)
    time.sleep(1)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=run, args=(i,))
        p_list.append(p)
        p.start()

    for i in range(10):
        p_list[i].join()

    print('Main Process Done ...',os.getpid())
    print(li)
默认无法共享数据

  在使用并发设计的时候最好尽可能的避免共享数据,尤其是在使用多进程的时候。 如果你真有需要 要共享数据, multiprocessing提供了两种方式。

1.Queues

  使用方法跟threading里的queue差不多

from multiprocessing import Process, Queue
 
def f(q):
    q.put([42, None, 'hello'])
 
if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

2.Pipe

  Pipe返回的是管道2边的对象:「父连接」和「子连接」。

  当子连接发送一个带有hello字符串的列表,父连接就会收到,所以parent_conn.recv()就会打印出来。这样就可以简单的实现在多进程之间传输Python内置的数据结构了。

from multiprocessing import Process, Pipe
 
def f(conn):
    conn.send([42, None, 'hello'])
    conn.close()
 
if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

内存共享:

  1.Array / Value

  数据可以用Value或Array存储在一个共享内存地图里,如下:

from multiprocessing import Process, Value, Array
 
def f(n, a):
    n.value = 3.1415927
    for i in range(len(a)):
        a[i] = -a[i]
 
if __name__ == '__main__':
    num = Value('d', 0.0)
    arr = Array('i', range(10))
 
    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()
 
    print(num.value)
    print(arr[:])

创建num和arr时,“d”和“i”参数由Array模块使用的typecodes创建:“d”表示一个双精度的浮点数,“i”表示一个有符号的整数,这些共享对象将被线程安全的处理。

Array(‘i’, range(10))中的‘i’参数:

‘c’: ctypes.c_char     ‘u’: ctypes.c_wchar    ‘b’: ctypes.c_byte     ‘B’: ctypes.c_ubyte
‘h’: ctypes.c_short     ‘H’: ctypes.c_ushort    ‘i’: ctypes.c_int      ‘I’: ctypes.c_uint
‘l’: ctypes.c_long,    ‘L’: ctypes.c_ulong    ‘f’: ctypes.c_float    ‘d’: ctypes.c_double
类型对应表

  2.Manage

  由Manager()返回的manager提供 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array类型的支持。

from multiprocessing import Process, Manager
 
def Foo(i,dic):
    dic[i] = 100 + i
    print(dic)
 
if __name__ == '__main__':
 
    manage = Manager()
    dic = manage.dict()
 
    for i in range(2):
        p = Process(target=Foo, args=(i,dic))
        p.start()
        p.join()

当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

同步机制:

  multiprocessing的Lock、Condition、Event、RLock、Semaphore等同步原语和threading模块的机制是一样的,用法也类似

#!/usr/bin/env python
# -*-coding:utf-8 -*-

from multiprocessing import Process, Array, RLock

def Foo(lock,temp,i):
    """
    将第0个数加100
    """
    lock.acquire()
    temp[0] = 100+i
    for item in temp:
        print(i,'----->',item)
    lock.release()


if __name__ == '__main__':
    lock = RLock()
    temp = Array('i', [11, 22, 33, 44])

    for i in range(20):
        p = Process(target=Foo,args=(lock,temp,i,))
        p.start()
进程锁实例

进程池

  进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。

  进程池中有两个方法:

  • apply

  • apply_async

from  multiprocessing  import Process,Pool
import time

def Foo(i):
    time.sleep(2)
    return i+100

def Bar(arg):
    print(arg,'--')

if __name__ == '__main__':

    pool = Pool(5)
    for i in range(10):
        pool.apply_async(func=Foo, args=(i,), callback=Bar)
        # pool.apply(func=Foo, args=(i,))
    
    # pool.map(Foo,[i for i in range(10)])
    # pool.map_async(Foo,[i for i in range(10)],callback=Bar)

    print('end')
    pool.close()
    pool.join()  # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

官方demo

from multiprocessing import Pool, TimeoutError
import time
import os
 
def f(x):
    return x*x
 
if __name__ == '__main__':
    # 创建4个进程 
    with Pool(processes=4) as pool:
 
        # 打印 "[0, 1, 4,..., 81]" 
        print(pool.map(f, range(10)))
 
        # 使用任意顺序输出相同的数字, 
        for i in pool.imap_unordered(f, range(10)):
            print(i)
 
        # 异步执行"f(20)" 
        res = pool.apply_async(f, (20,))      # 只运行一个进程 
        print(res.get(timeout=1))             # 输出 "400" 
 
        # 异步执行 "os.getpid()" 
        res = pool.apply_async(os.getpid, ()) # 只运行一个进程 
        print(res.get(timeout=1))             # 输出进程的 PID 
 
        # 运行多个异步执行可能会使用多个进程 
        multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
        print([res.get(timeout=1) for res in multiple_results])
 
        # 是一个进程睡10秒 
        res = pool.apply_async(time.sleep, (10,))
        try:
            print(res.get(timeout=1))
        except TimeoutError:
            print("发现一个 multiprocessing.TimeoutError异常")
 
        print("目前,池中还有其他的工作")
 
    # 退出with块中已经停止的池 
    print("Now the pool is closed and no longer available")
View Code
进程池的方法
  • apply(func[, args[, kwds]]) :使用arg和kwds参数调用func函数,结果返回前会一直阻塞,由于这个原因,apply_async()更适合并发执行,另外,func函数仅被pool中的一个进程运行。

  • apply_async(func[, args[, kwds[, callback[, error_callback]]]]) : apply()方法的一个变体,会返回一个结果对象。如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调时会调用callback,调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。

  • close() : 阻止更多的任务提交到pool,待任务完成后,工作进程会退出。

  • terminate() : 不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。

  • join() : wait工作线程的退出,在调用join()前,必须调用close() or terminate()。这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。

  • map(func, iterable[, chunksize])   注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。

  • map_async(func, iterable[, chunksize[, callback[, error_callback]]])

  • imap(func, iterable[, chunksize])

  • imap_unordered(func, iterable[, chunksize])

  • starmap(func, iterable[, chunksize])

  • starmap_async(func, iterable[, chunksize[, callback[, error_back]]])

 

多线程与多进程 理解与速度比较:                ?详情点击

 

协程

  协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程

  线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

  协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:

  协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

  协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

  协程的好处:

  • 无需线程上下文切换的开销

  • 无需原子操作锁定及同步的开销方便切换控制流,简化编程模型

    •   "原子操作(atomic operation)是不需要synchronized",所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

  缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

  协程一个标准定义,即符合什么条件就能称之为协程:

  1. 必须在只有一个单线程里实现并发

  2. 修改共享数据不需加锁

  3. 用户程序里自己保存多个控制流的上下文栈

  4. 一个协程遇到IO操作自动切换到其它协程

当我们说“上下文”的时候,指的是程序在执行中的一个状态。通常我们会用调用栈来表示这个状态——栈记载了每个调用层级执行到哪里,还有执行时的环境情况等所有有关的信息。

当我们说“上下文切换”的时候,表达的是一种从一个上下文切换到另一个上下文执行的技术。而“调度”指的是决定哪个上下文可以获得接下去的CPU时间的方法。

Python中的协程是通过“生成器(generator)”的概念实现的。

def consumer():         # 定义消费者,由于有yeild关键词,此消费者为一个生成器
    print("[Consumer] Init Consumer ......")
    r = "init ok"       # 初始化返回结果,并在启动消费者时,返回给生产者
    while True:
        n = yield r     # 消费者通过yield接收生产者的消息,同时返给其结果
        print("[Consumer] conusme n = %s, r = %s" % (n, r))
        r = "consume %s OK" % n     # 消费者消费结果,下个循环返回给生产者

def produce(c):         # 定义生产者,此时的 c 为一个生成器
    print("[Producer] Init Producer ......")
    r = c.send(None)    # 启动消费者生成器,同时第一次接收返回结果
    print("[Producer] Start Consumer, return %s" % r)
    n = 0
    while n < 5:
        n += 1
        print("[Producer] While, Producing %s ......" % n)
        r = c.send(n)   # 向消费者发送消息并准备接收结果。此时会切换到消费者执行
        print("[Producer] Consumer return: %s" % r)
    c.close()           # 关闭消费者生成器
    print("[Producer] Close Producer ......")

produce(consumer())

greenlet

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

gevent

  Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。

  Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0)
    print('Explicit context switch to foo again')

def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])

应用实例

from gevent import monkey; monkey.patch_all()
import gevent
import requests

def f(url):
    print('GET: %s' % url)
    resp = requests.get(url)
    data = resp.text
    print(url,len(data))


gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
])

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

import sys
import socket
import time
import gevent
 
from gevent import socket,monkey
monkey.patch_all()
 
 
def server(port):
    s = socket.socket()
    s.bind(('0.0.0.0', port))
    s.listen(500)
    while True:
        cli, addr = s.accept()
        gevent.spawn(handle_request, cli)
 
 
 
def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            print("recv:", data)
            conn.send(data)
            if not data:
                conn.shutdown(socket.SHUT_WR)
 
    except Exception as  ex:
        print(ex)
    finally:
        conn.close()
if __name__ == '__main__':
    server(8001)
Server
import socket
 
HOST = 'localhost'    # The remote host
PORT = 8001           # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"),encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)
    #print(data)
 
    print('Received', repr(data))
s.close()
Client
import socket
import threading

def sock_conn():

    client = socket.socket()

    client.connect(("localhost",8001))
    count = 0
    while True:
        #msg = input(">>:").strip()
        #if len(msg) == 0:continue
        client.send( ("hello %s" %count).encode("utf-8"))

        data = client.recv(1024)

        print("[%s]recv from server:" % threading.get_ident(),data.decode()) #结果
        count +=1
    client.close()


for i in range(100):
    t = threading.Thread(target=sock_conn)
    t.start()
并发100个连接

 

事件驱动与异步IO                    ?详情点击

 

补充:上下文管理

with open实质:

import contextlib

@contextlib.contextmanager
def Myopen(file_path,mode):
    f = open(file_path,mode)
    try:
        yield f
    finally:
        f.close()

with Myopen('1.txt','w')as f:
    f.write('1234')

  

 

转载于:https://www.cnblogs.com/5poi/p/6273461.html

你可能感兴趣的:(python,c/c++,操作系统)