Python——网络编程(四) 锁、信号量、线程、队列、进程

并发&并行


 

  • 并发(Concurrent): 是指一个处理器同时处理多个任务。

当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发。

  • 并行(Parallel): 是指多个处理器或者是多核的处理器同时处理多个不同的任务。

当系统有一个以上CPU时,则线程的操作有可能非并发.当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。

 

并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。 
来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。

 

同步&异步


 

同步是指:当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去。  
异步是指:当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响。

 

同步是指:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。  
异步是指:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。

 

举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

又如打电话时同步,发消息是异步

 

计算密集型(CPU-Intensive)


 

1、特点:要进行大量的计算,消耗CPU资源。比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。

2、计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

3、计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

 

IO密集型(IO-Intensive)


 

1、涉及到网络、磁盘IO的任务都是IO密集型任务。

2、特点:CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。

3、对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

4、IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

小补充:

  • time.sleep属于IO密集型,执行sleep后cpu并无其他操作,只在等睡眠的秒数过去

 

GIL (Global Interpreter Lock)


 

以下引自卢钧轶(cenalulu) 本文原文地址:http://cenalulu.github.io/python/gil-in-python/

 什么是GIL?

首先需要明确的一点是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。

 

那么CPython实现中的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.)

总的来说:

GIL是Global Interpreter Lock,即全局解释锁的缩写,保证了了同一时刻只有一个线程在一个CPU上执行字节码,无法将多个线程映射到多个CPU上。这是CPython解释器的缺陷,由于CPython是大部分环境下默认的Python执行环境,而很多库都是基于CPython编写的,因此很多人将GIL归结为Python的问题。

为什么会有GIL?

由于物理上得限制,各CPU厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。即使在CPU内部的Cache也不例外,为了有效解决多份缓存之间的数据同步时各厂商花费了不少心思,也不可避免的带来了一定的性能损失。

Python当然也逃不开,为了利用多核,Python开始支持多线程。而解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。 于是有了GIL这把超级大锁,而当越来越多的代码库开发者接受了这种设定后,他们开始大量依赖这种特性(即默认python内部对象是thread-safe的,无需在实现时考虑额外的内存锁和同步操作)。

慢慢的这种实现方式被发现是蛋疼且低效的。但当大家试图去拆分和去除GIL的时候,发现大量库代码开发者已经重度依赖GIL而非常难以去除了。有多难?做个类比,像MySQL这样的“小项目”为了把Buffer Pool Mutex这把大锁拆分成各个小锁也花了从5.5到5.6再到5.7多个大版为期近5年的时间,本且仍在继续。MySQL这个背后有公司支持且有固定开发团队的产品走的如此艰难,那又更何况Python这样核心开发和代码贡献者高度社区化的团队呢?

所以简单的说GIL的存在更多的是历史原因。如果推到重来,多线程的问题依然还是要面对,但是至少会比目前GIL这种方式会更优雅。

 

GIL的影响

从上文的介绍和官方的定义来看,GIL无疑就是一把全局排他锁。毫无疑问全局锁的存在会对多线程的效率有不小影响。甚至就几乎等于Python是个单线程的程序。
那么读者就会说了,全局锁只要释放的勤快效率也不会差啊。只要在进行耗时的IO操作的时候,能释放GIL,这样也还是可以提升运行效率的嘛。或者说再差也不会比单线程的效率差吧。理论上是这样,而实际上呢?
  • 对于CPU密集型任务而言,开多线程会比串行来得更慢,因为进程间的切换需要时间
  • 而对于IO密集型任务而言,开多线程的确能显著提高运行速度

如何解决


 

用multiprocessing替代Thread

multiprocessing库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

当然multiprocessing也不是万能良药。它的引入会增加程序实现时线程间数据通讯和同步的困难。就拿计数器来举例子,如果我们要多个线程累加同一个变量,对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本使得本来就非常痛苦的多线程程序编码,变得更加痛苦了。

用其他解析器

之前也提到了既然GIL只是CPython的产物,那么其他解析器是不是更好呢?没错,像JPython和IronPython这样的解析器由于实现语言的特性,他们不需要GIL的帮助。然而由于用了Java/C#用于解析器实现,他们也失去了利用社区众多C语言模块有用特性的机会。所以这些解析器也因此一直都比较小众。毕竟功能和性能大家在初期都会选择前者,Done is better than perfect

所以没救了么?

当然Python社区也在非常努力的不断改进GIL,甚至是尝试去除GIL。并在各个小版本中有了不少的进步。

  • 将切换颗粒度从基于opcode计数改成基于时间片计数
  • 避免最近一次释放GIL锁的线程再次被立即调度
  • 新增线程优先级功能(高优先级线程可以迫使其他线程释放所持有的GIL锁)

总结


 

Python GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。从本分的分析中,我们可以做以下一些简单的总结:

  • 因为GIL的存在,只有IO Bound场景下得多线程会得到较好的性能
  • 如果对并行计算性能较高的程序可以考虑把核心部分也成C模块,或者索性用其他语言实现
  • GIL在较长一段时间内将会继续存在,但是会不断对其进行改进

 

同步锁(互斥锁)


 

我们先来看一下一个实际操作中遇到的问题: 

需求是从100开始每次减1,重复100次;正常情况我们可以写一个串行,用for循环来处理,如果要写成多线程呢?

我开100个线程,每个线程进行减1操作,是不是就完成了这个需求?如下:(的确完成了这个需求)

 1 import threading
 2 
 3 def cal():
 4     global num
 5     num -= 1
 6 
 7 num = 100
 8 t_list = []
 9 for i in range(100):
10     t = threading.Thread(target=cal)
11     t_list.append(t)
12     t.start()
13     
14 for j in t_list:
15     j.join()
16 
17 print(num)          --> 0

那如果这个计算的需求中间可能还有代码,需要消耗一些时间,那么结果如何呢?(答案是没有完成我们的需求)

import threading
import time

def cal():
    global num
    temp = num
    time.sleep(0.1)
    num = temp - 1

num = 100
t_list = []
for i in range(100):
    t = threading.Thread(target=cal)
    t_list.append(t)
    t.start()

for j in t_list:
    j.join()

print(num)              --> 99

 

为什么会出现这种现象?

这就牵扯到多线程之间的切换问题了。当第一个num = 100赋值给线程1后,IO阻塞(这里是sleep0.1秒),还没进行下面的减1操作,就切换到线程2

此时线程2的num仍被赋值为100...依次下去。如果IO阻塞的时间足够长,那么100个线程都轮了一边也没执行下面的减1操作,等阻塞完毕的,所有线程的num都为100,然后再减1也就成了99

具体可以参考下图:

 

那么如何解决这个问题呢?答案是加同步锁

我把这个执行的代码块锁起来,这块代码只能被获得这个锁的用户执行,除非这个锁被释放然后被其他人拿到,否则谁也无法执行这个代码块

 1 def cal():
 2     global num
 3     #锁起来
 4     lock.acquire()
 5     temp = num
 6     time.sleep(0.01)
 7     num = temp - 1
 8     #将锁打开
 9     lock.release()
10 #创建锁的实例
11 lock = threading.Lock()
12 num = 100
13 t_list = []
14 for i in range(100):
15     t = threading.Thread(target=cal)
16     t_list.append(t)
17     t.start()
18 
19 for j in t_list:
20     j.join()
21 
22 print(num)              --> 0

你会说,这不是坑爹吗?这跟串行有什么区别?而且因为GIL的关系,甚至比直接串行来得慢。

被你说对了,这就是数据的安全性要求,无论哪门语言都得这么加锁处理;但是你只锁了这一部分呀,其他部分依旧是并发

线程死锁和递归锁


 

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

 1 import threading
 2 import time
 3 
 4 class MyTread(threading.Thread):
 5 
 6     def actionA(self):
 7 
 8         lockA.acquire()
 9         print('%s %s %s' %(self.name, 'gotA', time.ctime()))          #线程对象自带的name方法可以获得线程的名字
10         time.sleep(2)
11 
12         lockB.acquire()
13         print('%s %s %s' %(self.name, 'gotB', time.ctime()))
14         time.sleep(1)
15 
16         lockB.release()
17         lockA.release()
18 
19     def actionB(self):
20 
21         lockB.acquire()
22         print('%s %s %s' %(self.name, 'gotB', time.ctime()))  # 线程对象自带的name方法可以获得线程的名字
23         time.sleep(2)
24 
25         lockA.acquire()
26         print('%s %s %s' %(self.name, 'gotA', time.ctime()))
27         time.sleep(1)
28 
29         lockA.release()
30         lockB.release()
31 
32     def run(self):
33         self.actionA()
34         self.actionB()
35 
36 
37 if __name__ == '__main__':
38 
39     lockA = threading.Lock()
40     lockB = threading.Lock()
41 
42     t_list = []
43     for i in range(5):
44         t = MyTread()
45         t.start()
46         t_list.append(t)
47 
48     for i in t_list:
49         i.join()
50 
51     print('ending...')

如何解决?

解决方法:递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量

对于第获得Rlock的线程,只有这个线程可以继续aquire小的Rlock,并且counter记录了在这个线程里acquire的次数,其他线程调用这个锁时,counter不为0无法获得RLock

直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次

 

 1 import threading
 2 import time
 3 
 4 class MyTread(threading.Thread):
 5 
 6     def actionA(self):
 7 
 8         rlock.acquire()                          # count = 1
 9         print('%s %s %s' %(self.name, 'got Rlock1', time.ctime()))
10         time.sleep(2)
11 
12         rlock.acquire()                          # count = 2
13         print('%s %s %s' %(self.name, 'got Rlock2', time.ctime()))
14         time.sleep(1)
15 
16         rlock.release()                          # count = 1
17         rlock.release()                          # count = 0 此时RLock可重新被竞争获得
18 
19     def actionB(self):
20 
21         rlock.acquire()
22         print('%s %s %s' %(self.name, 'got Rlock2', time.ctime()))
23         time.sleep(2)
24 
25         rlock.acquire()
26         print('%s %s %s' %(self.name, 'got Rlock1', time.ctime()))
27         time.sleep(1)
28 
29         rlock.release()
30         rlock.release()
31 
32     def run(self):
33         self.actionA()
34         self.actionB()
35 
36 
37 if __name__ == '__main__':
38 
39     rlock = threading.RLock()
40 
41     t_list = []
42     for i in range(5):
43         t = MyTread()
44         t.start()
45         t_list.append(t)
46 
47     for i in t_list:
48         i.join()
49 
50     print('ending...')

同步对象


同步条件(Event)

在并发编程中我们有事会有这个需求:

当满足某个条件时线程执行同步块中的代码,条件不满足时,让线程在此等待,直至条件满足再执行同步代码块;

这其实就是一个线程间的通信问题

来看定义:

  • 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.

 

 1 import threading,time
 2 class Boss(threading.Thread):
 3     def run(self):
 4         print("BOSS:今晚大家都要加班到22:00。")
 5         print(event.isSet())        #False
 6         event.set()
 7         time.sleep(5)
 8         print("BOSS:<22:00>可以下班了。")
 9         print(event.isSet())        #False
10         event.set()
11 class Worker(threading.Thread):
12     def run(self):
13         event.wait()
14         print("Worker:哎……命苦啊\n", end='')
15         time.sleep(1)
16         event.clear()
17         event.wait()
18         print("Worker:OhYeah!\n", end='')
19 if __name__=="__main__":
20     event=threading.Event()
21     threads=[]
22     for i in range(5):
23         threads.append(Worker())
24     threads.append(Boss())
25     for t in threads:
26         t.start()
27     for t in threads:
28         t.join()
29     print('end')

 

信号量


 

信号量也是一把锁,它是用来控制线程并发数的;加了同步锁后只能允许一个线程同时运行,而加了信号量后可以指定n个线程同时运行

使用时主要有BoundedSemaphore和Semaphore两个方法

  • BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。
  • 计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
  • BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
 1 import threading, time
 2 class myThread(threading.Thread):
 3     def run(self):
 4         if semaphore.acquire():
 5             print(self.name)
 6             time.sleep(3)
 7             semaphore.release()
 8 if __name__=="__main__":
 9     semaphore=threading.Semaphore(5)   #不设置值的话默认为1
10     thrs=[]
11     for i in range(100):
12         thrs.append(myThread())
13     for t in thrs:
14         t.start()

 

多线程利器--队列(queue)


 

  • 列表在多线程或者多进程中,是一种不安全的数据结构,例子如下
 1 import threading, time
 2 
 3 li=[1, 2, 3, 4, 5]
 4 def pri():
 5     while li:
 6         a = li[-1]
 7         print(a)
 8         time.sleep(1)
 9         li.remove(a)
10 
11 t1=threading.Thread(target=pri)
12 t1.start()
13 t2=threading.Thread(target=pri)
14 t2.start()
instance

队列是数据安全的,那么如何使用队列呢?答案是使用queue模块

Python Queue模块有三种队列及构造函数:
1. Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
2. LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
3. 还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)
 1 import queue            #线程队列
 2 
 3 q = queue.Queue()       #queue.Queue(5)则表示队列最多存5个值,不填值则为无限
 4 
 5 q.put(111)
 6 q.put('hello')
 7 q.put({'name': 'ssy'})
 8 
 9 while True:
10     # get的时候如果队列中没值则会阻塞直到有值传入;同样如果put值进去超过了队列的设置大小也会阻塞,直到对联里面的值被取走后空出位置给你put
11     #get和put都有一个block参数默认为True,也就是上面那种效果(同步);如果将其设置为False,则以上情况出现时不会阻塞而是直接抛出异常
12     data = q.get()
13     print(data)
 1 import queue          
 2 
 3 q = queue.PriorityQueue()
 4 
 5 q.put([2, 111])
 6 q.put([1, 'hello'])
 7 q.put([3, {'name': 'ssy'}])
 8 
 9 while True:
10 
11     data = q.get(timeout=3)
12     print(data)
优先级队列

 

queue的其他方法:

  • q.qsize() 返回队列的大小(里面存放的元素个数,不是最大大小)
  • q.empty() 如果队列为空,返回True,反之False
  • q.full() 如果队列满了,返回True,反之False
  • q.full 与 maxsize 大小对应
  • Queue.get(itemblock=Truetimeout=None): 从队列里取数据。如果为空的话,blocking = False 直接报 empty异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能读取,报empty异常。
  • q.get_nowait() 相当q.get(False)
  • Queue.put(itemblock=Truetimeout=None): 往队列里放数据。如果满了的话,blocking = False 直接报 Full异常。如果blocking = True,就是等一会,timeout必须为 0 或正数。None为一直等下去,0为不等,正数n为等待n秒还不能存入,报Full异常。
  • q.put_nowait(item) 相当q.put(item, False)
  • q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
  • q.join() 实际上意味着等到队列为空,再执行别的操作

 

生产者消费者模型


 

为什么要使用生产者和消费者模式

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

什么是生产者消费者模式

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

 1 import threading, queue
 2 import time, random
 3 
 4 def productor():
 5 
 6     count = 0
 7     while count < 10:
 8         print('being producted...\n', end='')
 9         time.sleep(random.randrange(3))
10         print('product %s has been produced.\n' %count, end='')
11         q.put(count)
12         count += 1
13 
14 def consumer(name):
15 
16     count = 0
17     while count < 10:
18         time.sleep(random.randrange(4))
19         if not q.empty():
20             data = q.get()
21             print('\33[32;1mproduct %s has been consumed by consumer %s.\33[0m\n' %(data, name), end='')
22         else:
23             print('without product to be used.\n', end='')
24         count += 1
25 if __name__ == '__main__':
26 
27     q = queue.Queue()
28 
29     p = threading.Thread(target=productor, args=())         #生产者线程
30     c1 = threading.Thread(target=consumer, args=('A',))     #消费者线程
31     c2 = threading.Thread(target=consumer, args=('B',))     #消费者线程
32 
33     for i in (p, c1, c2):
34         i.start()
35     for i in (p, c1, c2):
36         i.join()
37 
38     print('end')

模拟消费者消费完生产者才进行生产这一过程(使用task_done、join方法;而不是empty判断)

 1 import threading, queue
 2 import time
 3 
 4 def productor():
 5 
 6     count = 0
 7     while count < 10:
 8         print('being producted...\n', end='')
 9         time.sleep(5)
10         print('product %s has been produced.\n' %count, end='')
11         q.put(count)
12         q.join()
13         count += 1
14 
15 def consumer(name):
16 
17     count = 0
18     while count < 10:
19 
20         data = q.get()
21         print('eating...\n', end='')
22         time.sleep(2)
23         q.task_done()
24         print('\33[32;1mproduct %s has been consumed by consumer %s.\33[0m\n' %(data, name), end='')
25         count += 1
26 
27 if __name__ == '__main__':
28 
29     q = queue.Queue()
30 
31     p = threading.Thread(target=productor, args=())         #生产者线程
32     c1 = threading.Thread(target=consumer, args=('A',))     #消费者线程
33     c2 = threading.Thread(target=consumer, args=('B',))     #消费者线程
34 
35     for i in (p, c1, c2):
36         i.start()
37     for i in (p, c1, c2):
38         i.join()
39 
40     print('end')
task_done、join的使用

 

多进程模块 multiprocessing


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

  • 注意:因为在windows下创建子进程会重新import启动它的这个文件,而在 import 的时候是会执行这些语句的。如果你这么写的话就会无限递归创建子进程报错。但是在multiprocessing.Process的源码中是对子进程再次产生子进程是做了限制的,是不允许的,于是出现如上的错误提示。所以必须把创建子进程的部分用那个 if 判断保护起来,import 的时候 name 不是 main ,就不会递归运行了。(否则出现以下错误)

 

1. 基本使用

 1 import threading
 2 import multiprocessing
 3 import time
 4 
 5 def f1():
 6     print('Threading', time.time())
 7     time.sleep(3)
 8 
 9 def f2():
10     print('Process:', time.time())
11     time.sleep(3)
12 
13 
14 if __name__ == '__main__':
15 
16     t_list, p_list = [], []
17     num = 4
18     for i in range(num):
19         t = threading.Thread(target=f1)
20         p = multiprocessing.Process(target=f2)
21         t_list.append(t)
22         p_list.append(p)
23 
24     for i in range(num):
25         # t_list[j].start()
26         p_list[j].start()
27 
28     for i in range(num):
29         # t_list[j].join()
30         p_list[j].join()
31 
32     print('end')
用法一
 1 class MyProcess(multiprocessing.Process):
 2 
 3     def __init__(self, num):
 4         super().__init__()
 5         self.num = num
 6 
 7     def run(self):
 8         print('hello', self.name, self.num)
 9 
10 if __name__ == '__main__':
11 
12     p_list = []
13     num = 3
14     for i in range(num):
15         p = MyProcess(i)
16         p_list.append(p)
17         p.start()
18 
19     for i in range(num):
20         p_list[i].join()
21 
22     print('end')
用法二
 1 import multiprocessing, time
 2 
 3 class MyProcess(multiprocessing.Process):
 4 
 5     def __init__(self, num):
 6         super().__init__()
 7         self.num = num
 8 
 9     def run(self):
10         print('hello', self.name, self.num)
11         time.sleep(2)
12 
13 if __name__ == '__main__':
14 
15     p_list = []
16     num = 3
17     for i in range(num):
18         p = MyProcess(i)
19         p.daemon = True     #守护线程里为: setDaemon(True)
20         p_list.append(p)
21         p.start()
22 
23     print('end')
守护进程(跟守护线程稍微有点不一样)

 2.进程开辟顺序,以pycharm运行为例

 

 

 1 import os
 2 from multiprocessing import Process
 3 
 4 def show_pid(name):
 5     print('-----%s-----' %name)
 6     print('parent process id: ',  os.getppid())
 7     print('process id: ', os.getpid())
 8 
 9 
10 if __name__ == '__main__':
11     
12     show_pid('Main_process')
13     p = Process(target=show_pid, args=('Sub_process 1',))
14     p.start()
15     p.join()
测试代码

 3.Process类

构造方法:

Process(group, target, name, args)

  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:进程号。

 4.进程通信

4.1进程队列

由于进程之间的资源不共享,所以需要通过传递进程队列的方式进行数据交换,(传递的队列相当于拷贝一份给别的进程,其内存地址不相同,但是存在着映射关系)

 

 1 import multiprocessing
 2 
 3 def func(queue):
 4     print(id(queue))
 5     for i in range(5):
 6         queue.put(i)
 7 
 8 if __name__ == '__main__':
 9 
10     q = multiprocessing.Queue()
11     print(id(q))
12     p = multiprocessing.Process(target=func, args=(q,))
13 
14     p.start()
15     p.join()
16 
17     for i in range(5):
18         print(q.get())

 

 

 

4.2管道

4.3Manager

posted @ 2020-01-09 17:08  Matrixssy  阅读(246)  评论(0)    收藏  举报