Python基础(十一)并发编程01

操作系统

概念

操作系统位于计算机硬件与应用软件之间,本质也是一个软件。操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成。

操作系统可以管理硬件设备,并将对硬件的管理封装成系统调用,并为用户和应用程序提供一个简单的接口,以便于使用

 

发展史

第一代计算机(1940-1955)

  真空管和穿孔卡片

  没有操作系统的概念,所有的程序设计都是直接操控硬件

  优点:程序员在申请的时间段内独享整个资源,可以即时地调试自己的程序(有bug可以立刻处理)

  缺点:浪费计算机资源,一个时间段内只有一个人用。

 第二代计算机(1955-1965)

  晶体管和批处理系统

  优点:批处理,节省了机时

  缺点:

  1.整个流程需要人参与控制,将磁带搬来搬去(中间俩小人)

  2.计算的过程仍然是顺序计算-》串行

  3.程序员原来独享一段时间的计算机,现在必须被统一规划到一批作业中,等待结果和重新调试的过程都需要等同批次的其他程序都运作完才可以(这极大的影响了程序的开发效率,无法及时调试程序

第三代计算机(1965-1980)

  集成电路芯片和多道程序设计

  开发出SPOOLING技术:不必将磁带搬来搬去了(中间俩小人失业了),强化了操作系统的功能

  多道程序设计:

    空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。

    时间上的复用:当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,类似于我们小学数学所学的统筹方法

    (操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走                   cpu的执行权限)

第四代计算机(1980~至今):个人计算机

进程和线程

进程

程序仅仅只是一堆代码而已,而进程指的是程序的运行过程(最小的资源单位,是抽象的概念)。

同一个程序执行两次,那也是两个进程

进程一般由程序、数据集、进程控制块三部分组成。

 线程

线程是最小的执行单位

 

进程和线程的关系

 1 一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)
 2 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
 3 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和
 4   程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 
 5 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调
 6   度的一个独立单位. 
 7   线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程
 8   自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是
 9   它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 
10   一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

 

python的GIL

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在执行的时候会淡定的在同一时刻只允许一个线程运行

一个进程中的多个线程同一时刻只能由一个cpu执行其中的一个线程,导致多核没有利用上,可以使用进程+协程来解决

 

 

对于IO密集型的任务,python的多线程是有意义的

对于计算密集型的任务,python的多线程就不推荐使用的,可以使用

可以使用多进程+协程达到多线程的效果

time.sleep()等同于IO操作

多线程与 threading模块

hreading 模块建立在thread 模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,提供了更方便的api来处理线程。

线程调用

直接调用

import threading
import time
 
def sayhi(num): #定义每个线程要运行的函数
 
    print("running on number:%s" %num)
 
    time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
 
    t1.start() #启动线程
    t2.start() #启动另一个线程
 
    print(t1.getName()) #获取线程名
    print(t2.getName())

继承式调用

t1.start()会默认调用t1.run()方法

以下函数共运行5s,若不使用线程则需要8s

import threading
import time


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

    def run(self):#定义每个线程要运行的函数

        print("running on number:%s" %self.num)

        time.sleep(3)

if __name__ == '__main__':

    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()
    
    print("ending......")

hreading.thread的实例方法

join&Daemon方法

t1.join() 在子线程t1完成运行之前,这个子线程的父线程将一直被阻塞

 1 import threading,time
 2 
 3 def eat(money): #t1
 4     print(time.ctime(), '  start to eat, need %s¥' %(money))
 5     time.sleep(3)
 6     print(time.ctime(), '  eating..')
 7     time.sleep(3)
 8     print(time.ctime(), '  end to eat agin')
 9 
10 def drink(money): #t2
11     print(time.ctime(), '  start to drink, need %s¥' %(money))
12     time.sleep(5)
13     print(time.ctime(), '  end to drink')
14 
15 
16 if __name__ == '__main__':
17     t1 = threading.Thread(target=eat, args=(15,)) #创建线程对象
18     # print(type(t1))
19     t2 = threading.Thread(target=drink, args=(1,))
20 
21     # t1.start() #将t1加入主线程,t1不用cpu时就暂时保存t1的运行状态,继续往下执行t2,待t1需要cpu时再次执行
22     # t2.start()
23 
24     t1.start() #将t1加入主线程,t1不用cpu时就暂时保存t1的运行状态,继续往下执行
25     t1.join() #继续执行t1至t1结束
26     print('这是有关线程的一些演示01')
27     t2.start()
28     print('这是有关线程的一些演示02')
29 
30     print('t1是否是活动的',t1.isAlive())
31     print('t1的线程名', t1.getName())
join

守护线程,在调用前设置.守护线程用来守护主线程,你有我有,你没我没.设置线程为守护线程,‘不重要’,主线程结束时该线程会随之结束

import threading
from time import ctime,sleep
import time

def ListenMusic(name):

        print ("Begin listening to %s. %s" %(name,ctime()))
        sleep(3)
        print("end listening %s"%ctime())

def RecordBlog(title):

        print ("Begin recording the %s! %s" %(title,ctime()))
        sleep(5)
        print('end recording %s'%ctime())


threads = []


t1 = threading.Thread(target=ListenMusic,args=('水手',))
t2 = threading.Thread(target=RecordBlog,args=('python线程',))

threads.append(t1)
threads.append(t2)

if __name__ == '__main__':
    # t1.setDaemon(True)
    # t2.setDaemon(True)

    for t in threads:
        #t.setDaemon(True) #注意:一定在start之前设置
        t.start()
        # t.join()
    # t1.join()
    # t1.setDaemon(True)

    # t2.join()########考虑这三种join位置下的结果?
    print ("all over %s" %ctime())
setDaemon

 

其他线程实例的方法

# run():  线程被cpu调度后自动执行线程对象的run方法
# start():启动线程活动。
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。

threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

 

同步锁

多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去所线程的意义)

 1 import time
 2 import threading
 3 
 4 def addNum():
 5     global num #在每个线程中都获取这个全局变量
 6     #num-=1
 7 
 8     temp=num
 9     #print('--get num:',num )
10     time.sleep(0.1)
11     num =temp-1 #对此公共变量进行-1操作
12 
13 num = 100  #设定一个共享变量
14 thread_list = []
15 for i in range(100):
16     t = threading.Thread(target=addNum)
17     t.start()
18     thread_list.append(t)
19 
20 for t in thread_list: #等待所有线程执行完毕
21     t.join()
22 
23 print('final num:', num )
资源被破环

使用同步锁解决,位于锁中的代码被执行完才能切换线程

 1 R=threading.Lock() #获取锁的对象
 2  
 3 ####
 4 def sub():
 5     global num
 6     R.acquire() #获得一把锁
 7     temp=num-1
 8     time.sleep(0.1)
 9     num=temp
10     R.release() #释放锁

递归锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:

 1 import threading,time
 2 
 3 class myThread(threading.Thread):
 4     def funA(self):
 5         lock_A.acquire()
 6         print(self.name, 'get lock_A', time.ctime())
 7         time.sleep(2)
 8         lock_B.acquire()
 9         print(self.name, 'get lock_B', time.ctime())
10         time.sleep(2)
11         lock_B.release()
12         lock_A.release()
13 
14     def funB(self):
15         lock_B.acquire()
16         print(self.name, 'get lock_B', time.ctime())
17         time.sleep(2)
18         lock_A.acquire()
19         print(self.name, 'get lock_A', time.ctime())
20         time.sleep(2)
21         lock_A.release()
22         lock_B.release()
23 
24     def run(self):
25         self.funA()
26         self.funB()
27 
28 th_li = []
29 lock_A = threading.Lock()
30 lock_B = threading.Lock()
31 
32 if __name__ == '__main__':
33     for i in range(4):
34         th_li.append(myThread())
35     for t in th_li:
36         t.start()
37     # 此时会产生死锁,一个线程得到了锁B,并等待锁A的释放,另一个线程得到了锁A,并等待锁B的释放,
38     # 这个时候双方都不会主动释放,造成死锁
39     # for t in th_li:
40     #     t.join()
死锁

 

lock=threading.RLock()

为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

 1 # 递归锁
 2 import threading,time
 3 
 4 class myThread(threading.Thread):
 5     def funA(self):
 6         lock.acquire()
 7         print(self.name, 'get lock_A', time.ctime())
 8         time.sleep(1)
 9         lock.acquire()
10         print(self.name, 'get lock_B', time.ctime())
11         time.sleep(1)
12         lock.release()
13         lock.release()
14 
15     def funB(self):
16         lock.acquire()
17         print(self.name, 'get lock_B', time.ctime())
18         time.sleep(1)
19         lock.acquire()
20         print(self.name, 'get lock_A', time.ctime())
21         time.sleep(1)
22         lock.release()
23         lock.release()
24 
25     def run(self):
26         self.funA()
27         self.funB()
28 
29 
30 lock = threading.RLock() #递归锁
31 th_li = []
32 if __name__ == '__main__':
33     for i in range(4):
34         th_li.append(myThread())
35     for t in th_li:
36         t.start()
使用递归锁

 

 

 同步条件(Event)

利用Event可以设置进程A必须等待进程B的某部分代码执行结束才能继续执行A

An event is a simple synchronization object;the event represents an internal flag,

and threads can wait for the flag to be set, or set or clear the flag themselves.


event = threading.Event()

# a client thread can wait for the flag to be set
event.wait()

# a server thread can set or reset it
event.set()
event.clear()


If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event.

import time

import threading

class Account:
    def __init__(self, _id, balance):
        self.id = _id
        self.balance = balance
        self.lock = threading.RLock()

    def withdraw(self, amount):

        with self.lock:
            self.balance -= amount

    def deposit(self, amount):
        with self.lock:
            self.balance += amount


    def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景

        with self.lock:
            interest=0.05
            count=amount+amount*interest

            self.withdraw(count)


def transfer(_from, to, amount):

    #锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的
     _from.withdraw(amount)

     to.deposit(amount)



alex = Account('alex',1000)
yuan = Account('yuan',1000)

t1=threading.Thread(target = transfer, args = (alex,yuan, 100))
t1.start()

t2=threading.Thread(target = transfer, args = (yuan,alex, 200))
t2.start()

t1.join()
t2.join()

print('>>>',alex.balance)
print('>>>',yuan.balance)
View Code

 

信号量(Semaphore)

信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。

  计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)

  BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

import threading,time
class myThread(threading.Thread):
    def run(self):
        if semaphore.acquire():
            print(self.name)
            time.sleep(5)
            semaphore.release()
if __name__=="__main__":
    semaphore=threading.Semaphore(5) #设置线程并发数为5
    thrs=[]
    for i in range(100):
        thrs.append(myThread())
    for t in thrs:
        t.start()
View Code

 多线程与队列Queue

 1 import threading,time
 2 
 3 li=[1,2,3,4,5]
 4 
 5 def pri():
 6     while li:
 7         a=li[-1]
 8         print(a)
 9         time.sleep(1)
10         try:
11             li.remove(a)
12         except Exception as e:
13             print('----',a,e)
14 
15 t1=threading.Thread(target=pri,args=())
16 t1.start()
17 t2=threading.Thread(target=pri,args=())
18 t2.start()
列表是不安全的数据结构

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

 1 创建一个“队列”对象
 2 import Queue
 3 q = Queue.Queue(maxsize = 10)
 4 Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
 5 
 6 将一个值放入队列中
 7 q.put(10)
 8 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
 9 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。
10 
11 将一个值从队列中取出
12 q.get()
13 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
14 get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
15 
16 Python Queue模块有三种队列及构造函数:
17 1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
18 2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
19 3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)
20 
21 此包中的常用方法(q = Queue.Queue()):
22 q.qsize() 返回队列的大小
23 q.empty() 如果队列为空,返回True,反之False
24 q.full() 如果队列满了,返回True,反之False
25 q.full 与 maxsize 大小对应
26 q.get([block[, timeout]]) 获取队列,timeout等待时间
27 q.get_nowait() 相当q.get(False)
28 非阻塞 q.put(item) 写入队列,timeout等待时间
29 q.put_nowait(item) 相当q.put(item, False)
30 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
31 q.join() 实际上意味着等到队列为空,再执行别的操作

 

import queue

#先进后出

q=queue.LifoQueue()

q.put(34)
q.put(56)
q.put(12)

#优先级
# q=queue.PriorityQueue()
# q.put([5,100])
# q.put([7,200])
# q.put([3,"hello"])
# q.put([4,{"name":"alex"}])

while 1:

  data=q.get()
  print(data)

 

 1 import time,random
 2 import queue,threading
 3 
 4 q = queue.Queue()
 5 
 6 def Producer(name):
 7   count = 0
 8   while count <10:
 9     print("making........")
10     time.sleep(random.randrange(3))
11     q.put(count)
12     print('Producer %s has produced %s baozi..' %(name, count))
13     count +=1
14     #q.task_done()
15     #q.join()
16     print("ok......")
17 def Consumer(name):
18   count = 0
19   while count <10:
20     time.sleep(random.randrange(4))
21     if not q.empty():
22         data = q.get()
23         #q.task_done()
24         #q.join()
25         print(data)
26         print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
27     else:
28         print("-----no baozi anymore----")
29     count +=1
30 
31 p1 = threading.Thread(target=Producer, args=('A',))
32 c1 = threading.Thread(target=Consumer, args=('B',))
33 # c2 = threading.Thread(target=Consumer, args=('C',))
34 # c3 = threading.Thread(target=Consumer, args=('D',))
35 p1.start()
36 c1.start()
37 # c2.start()
38 # c3.start()
生产者消费者模型

 

 多进程 multiprocessing

Multiprocessing is a package that supports spawning processes using an API similar to the threading module. The multiprocessing package offers both local and remote concurrency,effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine. It runs on both Unix and Windows.

由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

进程调用

直接调用

 1 from multiprocessing import Process
 2 import time
 3 def f(name):
 4     time.sleep(1)
 5     print('hello', name,time.ctime())
 6 
 7 if __name__ == '__main__':
 8     p_list=[]
 9     for i in range(3):
10         p = Process(target=f, args=('alvin',))
11         p_list.append(p)
12         p.start()
13     for i in p_list:
14         p.join()
15     print('end')

 继承式调用

 1 from multiprocessing import Process
 2 import time
 3 
 4 class MyProcess(Process):
 5     def __init__(self):
 6         super(MyProcess, self).__init__()
 7         #self.name = name
 8 
 9     def run(self):
10         time.sleep(1)
11         print ('hello', self.name,time.ctime())
12 
13 
14 if __name__ == '__main__':
15     p_list=[]
16     for i in range(3):
17         p = MyProcess()
18         p.start()
19         p_list.append(p)
20 
21     for p in p_list:
22         p.join()
23 
24     print('end')

To show the individual process IDs involved, here is an expanded example:

from multiprocessing import Process
import os
import time
def info(title):
  
    print("title:",title)
    print('parent process:', os.getppid())
    print('process id:', os.getpid())

def f(name):
    info('function f')
    print('hello', name)

if __name__ == '__main__':
    info('main process line')
    time.sleep(1)
    print("------------------")
    p = Process(target=info, args=('yuan',))
    p.start()
    p.join()
View Code

process类

构造方法:

Process([group [, target [, name [, args [, kwargs]]]]])

  group: 线程组,目前还没有实现,库引用中提示必须是None; 
  target: 要执行的方法; 
  name: 进程名; 
  args/kwargs: 要传入方法的参数。

实例方法:

  is_alive():返回进程是否在运行。

  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。

  start():进程准备就绪,等待CPU调度

  run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。

  terminate():不管任务是否完成,立即停止工作进程

属性:

  daemon:和线程的setDeamon功能一样

  name:进程名字。

  pid:进程号。

 1 import time
 2 from  multiprocessing import Process
 3 
 4 def foo(i):
 5     time.sleep(1)
 6     print (p.is_alive(),i,p.pid)
 7     time.sleep(1)
 8 
 9 if __name__ == '__main__':
10     p_list=[]
11     for i in range(10):
12         p = Process(target=foo, args=(i,))
13         #p.daemon=True
14         p_list.append(p)
15 
16     for p in p_list:
17         p.start()
18     # for p in p_list:
19     #     p.join()
20 
21     print('main process end')
View Code

进程间的通讯

一、进程队列Queue

 1 from multiprocessing import Process, Queue
 2 import queue
 3 
 4 def f(q,n):
 5     #q.put([123, 456, 'hello'])
 6     q.put(n*n+1)
 7     print("son process",id(q))
 8 
 9 if __name__ == '__main__':
10     q = Queue()  #try: q=queue.Queue()
11     print("main process",id(q))
12 
13     for i in range(3):
14         p = Process(target=f, args=(q,i))
15         p.start()
16 
17     print(q.get())
18     print(q.get())
19     print(q.get())

二、管道

The Pipe() function returns a pair of connection objects connected by a pipe which by default is duplex (two-way). For example:

from multiprocessing import Process, Pipe

def f(conn):
    conn.send([12, {"name":"yuan"}, 'hello'])
    response=conn.recv()
    print("response",response)
    conn.close()
    print("q_ID2:",id(child_conn))

if __name__ == '__main__':

    parent_conn, child_conn = Pipe()
    print("q_ID1:",id(child_conn))
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    parent_conn.send("儿子你好!")
    p.join()

The two connection objects returned by Pipe() represent the two ends of the pipe. Each connection object has send() and recv() methods (among others). Note that data in a pipe may become corrupted if two processes (or threads) try to read from or write to the same end of the pipe at the same time. Of course there is no risk of corruption from processes using different ends of the pipe at the same time.

三、Managers

Queue和pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.

A manager returned by Manager() will support types listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue and Array. For example:

from multiprocessing import Process, Manager

def f(d, l,n):
    d[n] = '1'
    d['2'] = 2
    d[0.25] = None
    l.append(n)
    #print(l)

    print("son process:",id(d),id(l))

if __name__ == '__main__':

    with Manager() as manager:

        d = manager.dict()

        l = manager.list(range(5))

        print("main process:",id(d),id(l))

        p_list = []

        for i in range(10):
            p = Process(target=f, args=(d,l,i))
            p.start()
            p_list.append(p)

        for res in p_list:
            res.join()

        print(d)
        print(l)

进程同步

Without using the lock output from the different processes is liable to get all mixed up.

from multiprocessing import Process, Lock


def f(l, i):
        l.acquire()
        print('hello world %s' % i)
        l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

 

进程池

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

进程池中有两个方法:

  • apply
  • apply_async
from  multiprocessing import Process,Pool
import time,os

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

def Bar(arg):

    print(os.getpid())
    print(os.getppid())
    print('logger:',arg)

pool = Pool(5)

Bar(1)
print("----------------")

for i in range(10):
    #pool.apply(func=Foo, args=(i,))
    #pool.apply_async(func=Foo, args=(i,))
    pool.apply_async(func=Foo, args=(i,),callback=Bar)

pool.close()
pool.join()
print('end')

协程

协程,又称微线程,纤程。英文名Coroutine。

进程和线程的运行都是抢占式的

协程是协作式的(非抢占式),程序的运行先后顺序我们可以完全控制

 

协程本质上就只有一个线程,主要解决IO操作

 

优点:

优点1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

yield的简单实现

 1 import time
 2 import queue
 3 
 4 def consumer(name):
 5     print("--->ready to eat baozi...")
 6     while True:
 7         new_baozi = yield
 8         print("[%s] is eating baozi %s" % (name,new_baozi))
 9         #time.sleep(1)
10 
11 def producer():
12 
13     r = con.__next__()
14     r = con2.__next__()
15     n = 0
16     while 1:
17         time.sleep(1)
18         print("\033[32;1m[producer]\033[0m is making baozi %s and %s" %(n,n+1) )
19         con.send(n)
20         con2.send(n+1)
21 
22         n +=2
23 
24 
25 if __name__ == '__main__':
26     con = consumer("c1")
27     con2 = consumer("c2")
28     p = producer()

Greenlet

greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

 1 from greenlet import greenlet
 2  
 3  
 4 def test1():
 5     print(12)
 6     gr2.switch()
 7     print(34)
 8     gr2.switch()
 9  
10  
11 def test2():
12     print(56)
13     gr1.switch()
14     print(78)
15  
16  
17 gr1 = greenlet(test1)
18 gr2 = greenlet(test2)
19 gr1.switch()

Gevent

协程: 遇见IO操作会自动切换进行下一个需要cpu的操作

 1 import gevent
 2 
 3 import requests,time
 4 
 5 
 6 start=time.time()
 7 
 8 def f(url):
 9     print('GET: %s' % url)
10     resp =requests.get(url)
11     data = resp.text
12     print('%d bytes received from %s.' % (len(data), url))
13 
14 gevent.joinall([
15 
16         gevent.spawn(f, 'https://www.python.org/'),
17         gevent.spawn(f, 'https://www.yahoo.com/'),
18         gevent.spawn(f, 'https://www.baidu.com/'),
19         gevent.spawn(f, 'https://www.sina.com.cn/'),
20 
21 ])
22 
23 # f('https://www.python.org/')
24 #
25 # f('https://www.yahoo.com/')
26 #
27 # f('https://baidu.com/')
28 #
29 # f('https://www.sina.com.cn/')
30 
31 print("cost time:",time.time()-start)

 

 

Python中的上下文管理器(contextlib模块)>

上下文管理器的任务是:代码块执行前准备,代码块执行后收拾

如何使用上下文管理器

如何打开一个文件,并写入"hello world"

filename="my.txt"
mode="w"
f=open(filename,mode)
f.write("hello world")
f.close()

当发生异常时(如磁盘写满),就没有机会执行第5行。当然,我们可以采用try-finally语句块进行包装:

writer=open(filename,mode)
try:
    writer.write("hello world")
finally:
    writer.close()

当我们进行复杂的操作时,try-finally语句就会变得丑陋,采用with语句重写:

with open(filename,mode) as writer:
    writer.write("hello world")

as指代了从open()函数返回的内容,并把它赋给了新值。with完成了try-finally的任务。

自定义上下文管理器

with语句的作用类似于try-finally,提供一种上下文机制。要应用with语句的类,其内部必须提供两个内置函数__enter__和__exit__。前者在主体代码执行前执行,后者在主体代码执行后执行。as后面的变量,是在__enter__函数中返回的。

 1 class echo():
 2     def output(self):
 3         print "hello world"
 4     def __enter__(self):
 5         print "enter"
 6         return self  #可以返回任何希望返回的东西
 7     def __exit__(self,exception_type,value,trackback):
 8         print "exit"
 9         if exception_type==ValueError:
10             return True
11         else:
12             return Flase
13   
14 >>>with echo as e:
15     e.output()
16      
17 输出:
18 enter
19 hello world
20 exit

完备的__exit__函数如下:

def __exit__(self,exc_type,exc_value,exc_tb)

其中,exc_type:异常类型;exc_value:异常值;exc_tb:异常追踪信息

当__exit__返回True时,异常不传播

contextlib模块

contextlib模块的作用是提供更易用的上下文管理器,它是通过Generator实现的。contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制,常用框架如下:

 1 from contextlib import contextmanager
 2 @contextmanager
 3 def make_context():
 4     print 'enter'
 5     try:
 6         yield "ok"
 7     except RuntimeError,err:
 8         print 'error',err
 9     finally:
10         print 'exit'
11          
12 >>>with make_context() as value:
13     print value
14      
15 输出为:
16     enter
17     ok
18     exit

其中,yield写入try-finally中是为了保证异常安全(能处理异常)as后的变量的值是由yield返回。yield前面的语句可看作代码块执行前操作,yield之后的操作可以看作在__exit__函数中的操作。

以线程锁为例:

@contextlib.contextmanager
def loudLock():
    print 'Locking'
    lock.acquire()
    yield
    print 'Releasing'
    lock.release()
 
with loudLock():
    print 'Lock is locked: %s' % lock.locked()
    print 'Doing something that needs locking'
 
#Output:
#Locking
#Lock is locked: True
#Doing something that needs locking
#Releasing

contextlib.nested:减少嵌套

对于

with open(filename,mode) as reader:
    with open(filename1,mode1) as writer:
        writer.write(reader.read())

可以通过contextlib.nested进行简化:

with contextlib.nested(open(filename,mode),open(filename1,mode1)) as (reader,writer):
    writer.write(reader.read())

在python 2.7及以后,被一种新的语法取代:

with open(filename,mode) as reader,open(filename1,mode1) as writer:
    writer.write(reader.read())

contextlib.closing()

file类直接支持上下文管理器API,但有些表示打开句柄的对象并不支持,如urllib.urlopen()返回的对象。还有些遗留类,使用close()方法而不支持上下文管理器API。为了确保关闭句柄,需要使用closing()为它创建一个上下文管理器(调用类的close方法)。

文章参考

https://www.cnblogs.com/yuanchenqi/articles/6248025.html

https://www.cnblogs.com/linhaifeng/articles/6817679.html#_label1

https://www.cnblogs.com/linhaifeng/articles/7430066.html

https://www.cnblogs.com/yuanchenqi/articles/5722574.html

 

 


posted @ 2019-10-14 15:00  西伯利亚狼dreamer  阅读(236)  评论(0编辑  收藏  举报