Python多进程与多线程

一、基本概念html

二、多线程内容方法python

三、多进程内容方法编程

一、基本概念

1.1 线程

1.1.1 什么是线程

线程是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,安全

一个进程中能够并发多个线程,每条线程并行执行不一样的任务。A thread is an execution context, which is all the information a CPU needs to多线程

execute a stream of instructions.(一个线程是一个execution context(执行上下文),即一个cpu执行时所须要的一串指令。)并发

1.1.2 线程如何工做

Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped.One way toapp

achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.异步

If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back,socket

and resume it from where you were.async

Threads work in the same way. A CPU is giving you the illusion(幻觉) that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can

do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.

On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers.

1.2 进程

一个程序的执行实例就是一个进程。每个进程提供执行程序所需的全部资源。(进程本质上是资源的集合)

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique

process identifier, environment variables, a priority class, minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread,

often called the primary thread, but can create additional threads from any of its threads.

1.3 进程与线程的区别

  1. Threads share the address space of the process that created it; processes have their own address space.
  2. Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
  3. Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes.
  4. New threads are easily created; new processes require duplication of the parent process.
  5. Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.
  6. Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

 

  1. 同一个进程中的线程共享同一内存空间,可是进程之间是独立的。
  2. 同一个进程中的全部线程的数据是共享的(进程的数据段),进程之间的数据是独立的(进程有本身的父进程数据段的副本)。
  3. 同一个进程的线程之间能够直接通讯,可是进程之间的交流须要借助中间代理来实现。
  4. 建立新的线程很容易,可是建立新的进程须要对父进程作一次复制。
  5. 一个线程能够操做同一进程的其余线程,可是进程只能操做其子进程。
  6. 对主线程的修改可能会影响其余线程的行为,可是父进程的修改(除了删除之外)不会影响其余子进程。
  7. 线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
  8. 线程启动速度快,进程启动速度慢(可是二者运行速度没有可比性)。

二、多线程

 2.1 threading模块的初级使用

Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数状况下,咱们只须要使用threading这个高级模块。

Python中使用线程有两种方式:函数或者用类来包装线程对象。

2.1.1 函数式建立

启动一个线程就是把一个函数传入并建立Thread实例,语法以下:

t = threading.Thread(target=function, args=(kwargs,))

参数说明:

  • t - 赋值变量
  • function - 线程函数。
  • args - 传递给线程函数的参数,他必须是个tuple类型。
  • kwargs - 可选参数。

而后调用start()开始执行:

import threading
import time


def task(n):
    print('task---%s' % n)
    time.sleep(2)
    print('End')


t1 = threading.Thread(target=task, args=('t1',))
t2 = threading.Thread(target=task, args=('t2',))
t1.start()
t2.start()

执行结果以下:

task---t1
task---t2
End
End

 2.1.2 继承式建立

threading 模块提供的一些方法:

  • threading.current_thread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.active_count(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块一样提供了Thread类来处理线程,Thread类提供了如下方法:

方法 注释
run() 线程被cpu调度后自动执行线程对象的run方法,若是想自定义线程类,直接重写run方法就好了
start() 启动线程活动
join() 逐个执行每一个线程,执行完毕后继续往下执行
isAlive() 返回线程是否活动的
getName() 返回线程名
setName() 设置线程名
setDaemon(True) 设置为守护线程

 

使用Threading模块建立线程,直接从threading.Thread继承,而后重写__init__方法和run方法:

import threading
import time


class MyThreading(threading.Thread):      #继承父类threading.Thread
    def __init__(self, n):
        super(MyThreading, self).__init__()
        self.n = n

    def run(self):          #重构run()方法
        print('running task', self.n)
        time.sleep(1)
        print('End')


if __name__ == '__main__':
    t1 = MyThreading('t1')
    t2 = MyThreading('t2')
    t1.start()      #启动线程后,会自动执行run()方法
    t2.start()
    print('The %s has finished' % threading.current_thread)     #打印当前线程变量

执行结果以下:

running task t1
running task t2
The <_MainThread(MainThread, started 13204)> has finished
End
End

补充:

import threading
import time


class MyThreading(threading.Thread):
    def __init__(self, n):
        super(MyThreading, self).__init__()
        self.n = n

    def run(self):
        print('running task', self.n)
        time.sleep(1)
        print('End')


if __name__ == '__main__':
    t1 = MyThreading('t1')
    t2 = MyThreading('t2')
    t1.start()
    t2.start()
    t1.join()      #主线程等待t1的线程完成再往下执行
    t2.join()
    print('The %s has finished' % threading.current_thread())


# 执行结果以下(可对比上一个程序的结果,join()后主线程等待子线程完成再打印):
running task t1
running task t2
End
End
The <_MainThread(MainThread, started 11868)> has finished
View Code

 2.1.3 几个经常使用方法

下面经过一个记录线程的例子,进一步了解join()方法和threading.current_thread()、threading.active_count()方法:

import threading
import time


def task(n):
    print('task---%s' % n)   # 打印子线程
    time.sleep(1)
    print('task %s done,the number of active threads is:%s' % (threading.current_thread(), threading.active_count()))   # 打印当前线程变量和活跃线程个数

start_time = time.time()     # 记录开始时刻
t_obj = []     # 定义列表存放线程实例
for i in range(5):       # 设置5个线程实例
    t = threading.Thread(target=task, args=('%s' % i,))
    t.start()
    t_obj.append(t)     # 将全部线程存放在列表中
for t in t_obj:      # 为全部子线程添加join方法,使主线程等待全部线程完成
    t.join()
stop_time = time.time()      # 记录结束时刻
print('------all threads has finished------', threading.current_thread(), threading.active_count())    # 打印主线程和线程个数
print('const:', stop_time - start_time)      # 打印总耗时

执行结果以下:

task---0
task---1
task---2
task---3
task---4
task <Thread(Thread-4, started daemon 11868)> done,the number of active threads is:6
task <Thread(Thread-2, started daemon 7596)> done,the number of active threads is:5
task <Thread(Thread-3, started daemon 8488)> done,the number of active threads is:4
task <Thread(Thread-5, started daemon 9648)> done,the number of active threads is:3
task <Thread(Thread-1, started daemon 15396)> done,the number of active threads is:2
------all threads has finished------ <_MainThread(MainThread, started 15196)> 1
const: 1.0155258178710938

注意到这里的threading.active_count()方法返回的:线程数量=子线程的数量 + 主线程数量(1)

2.2 守护线程

来看一个简单的守护线程的例子,这里使用setDaemon(True)把全部的子线程都变成了主线程的守护线程,所以当主线程结束后,子线程也会随之结束。因此当主线程结束后,整个程序就退出了。

import threading
import time


def task(n):
    print('task-%s starts' % n)
    time.sleep(2)
    print('task-%s end' % n)

for i in range(3):
    t = threading.Thread(target=task, args=('%s' % i,))
    t.setDaemon(True)       # 应在线程启动前设置守护线程
    t.start()

print('The main thread end')

 执行结果以下:

task-0 starts
task-1 starts
task-2 starts
The main thread end

对比不设置守护进程:

import threading
import time


def task(n):
    print('task-%s starts' % n)
    time.sleep(2)
    print('task-%s end' % n)

for i in range(3):
    t = threading.Thread(target=task, args=('%s' % i,))
    t.start()

print('The main thread end')


# 执行结果以下:
task-0 starts
task-1 starts
task-2 starts
The main thread end
task-0 end
task-1 end
task-2 end
View Code

 因此,将子线程设置守护线程后,主线程结束,杀死未执行完的子线程,程序退出;未设置守护线程,主线程结束,可是并无杀死子线程,子线程依然能够继续执行,直到子线程所有结束,程序退出。

关于更多join()和守护线程可移步:python多线程中join()的理解

2.3 GIL

在非python环境中,单核状况下,同时只能有一个任务执行。多核时能够支持多个线程同时执行。可是在python中,不管有多少核,同时只能执行一个线程。

究其缘由,这就是因为GIL的存在致使的。

GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所作的决定。某个线程想要执行,必须先拿到GIL,咱们

能够把GIL看做是“通行证”,而且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不容许进入CPU执行。GIL只在cpython中才有,由于cpython

调用的是c语言的原生线程,因此他不能直接操做cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。

Python多线程的工做过程:
python在使用多线程的时候,调用的是c语言的原生线程。

    1. 拿到公共数据
    2. 申请gil
    3. python解释器调用os原生线程
    4. os操做cpu执行运算
    5. 当该线程执行时间到后,不管运算是否已经执行完,gil都被要求释放
    6. 进而由其余进程重复上面的过程
    7. 等其余进程执行完后,又会切换到以前的线程(从他记录的上下文继续执行)
      整个过程是每一个线程执行本身的运算,当执行时间到就进行切换(context switch)。

2.4 线程锁(互斥锁mutex)

多线程和多进程最大的不一样在于,多进程中,同一个变量,各自有一份拷贝存在于每一个进程中,互不影响,而多线程中,全部变量都由全部线程共享,因此,任何

一个变量均可以被任何一个线程修改,所以,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

举个小例子看下:

import threading


# 你开始在坐标原点
position = 0

def move(x):        # 定义你的移动
    # 向右为正,向左为负
    global position
    position = position + x
    position = position - x    # 回到原点


def run_thread(x):
    for i in range(500000):
        move(x)

t1 = threading.Thread(target=run_thread, args=(3,))
t2 = threading.Thread(target=run_thread, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
print('your position:', position)

这里定义了一个position,在move()方法中让它左右移动,理论上不管调用多少次move()方法,position的值最终都是0,可是,因为线程的调度是由操做系统决定的,

当t一、t2交替执行时,只要循环次数足够多,position的结果就不必定是0了。

究竟为何会出现结果不为0的状况呢?由于cpu在执行move()方法时,须要执行若干条语句已达到修改的目的,而此过程当中,线程可能随时中断(线程交替执行),

好比,position = position + 2,执行时会分步执行:

  1. 计算position + 2,存入临时变量中;
  2. 将临时变量的值赋给position。

能够写为:

y = position + x
position = y

因为y是局部变量,两个线程各自都有本身的y,t1和t2是交替运行的,若是操做系统如下面的顺序执行t一、t2:

# 初始值 position = 0

t1: y1 = position + 3  # y1 = 0 + 3 = 3

t2: y2 = position + 2  # y2 = 0 + 2 = 2
t2: position = y2        # position = 2

t1: position = y1        # position = 3
t1: y1 = position - 3   # y1 = 3 - 3 = 0
t1: position = y1        # position = 0

t2: y2 = position - 8   # y2 = 0 - 2 = -2
t2: position = y2        # position = -2

# 结果 position = -2

(注:虽然解释器有GIL,同一时间只能有一个线程执行,但数据会被copy成不少份,线程切换过程当中,仍是会有数据出错)

若是要确保position计算正确,就要给move()上一把锁,当某个线程开始执行move()时,就称该线程由于得到了锁,所以其余线程不能同时执行move(),只能等待,

直到锁被释放后,得到该锁之后才能改。因为锁只有一个,不管多少线程,同一时刻最多只有一个线程持有该锁,因此,不会形成修改的冲突。建立一个锁就是

经过threading.Lock()来实现:

import threading

position = 0
lock = threading.Lock()      # 实例化一把锁

def move(x):   
    lock.acquire()     # 获取锁
    global position
    position = position + x
    position = position - x    
    lock.release()      # 释放锁


def run_thread(x):
    for i in range(500000):
        move(x)

t1 = threading.Thread(target=run_thread, args=(3,))
t2 = threading.Thread(target=run_thread, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
print('your position:', position)

当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,而后继续执行代码,其余线程就继续等待直到得到锁为止。

锁的好处就是确保了某段关键代码只能由一个线程从头至尾完整地执行,坏处就是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就

大大地降低了。

2.5 递归锁

RLcok类的用法和Lock类如出一辙,但它支持嵌套,在多个锁没有释放的时候通常会使用使用RLcok类。

import threading

lock = threading.RLock()    # 实例化一个递归锁
num = 0


def task():
    print('task is working')
    lock.acquire()    
    global num
    num += 1
    lock.release()     
    return num


def foo():
    print('foo is working')
    lock.acquire()
    res = task()
    print('acquire num from task:',res)
    lock.release()


for i in range(3):
    t = threading.Thread(target=foo)
    t.start()

有锁的嵌套,必定要使用递归锁,若是还使用互斥锁,会出现锁死的状况。

2.6 信号量(Semaphore)

 线程(互斥)锁同时只容许一个线程更改数据,而Semaphore是同时容许必定数量的线程更改数据 ,好比厕全部3个坑,那最多只容许3我的上厕所,后面的人只能等里

面有人出来了才能再进去。

import threading, time


def run(n):
    semaphore.acquire()        # 获取信号量
    time.sleep(1)
    print("run the thread: %s\n" % 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()

2.7 计时器(Timer)

This class represents an action that should be run only after a certain amount of time has passed 

Timers are started, as with threads, by calling their start() method. The timer can be stopped (before its action has begun) by calling the cancel() method.

The interval the timer will wait before executing its action may not be exactly the same as the interval specified by the user.

from threading import Timer

def wait():
    print('I have wait 2 seconds')

t = Timer(2, wait)
t.start()       # 2秒以后执行wait()

2.8 事件(Event)

python线程的事件用于主线程控制其余线程的执行,事件是一个简单的线程同步对象,对象包含一个可由线程设置的信号标志(flag),它容许线程等待某些事件的发生。

在初始状况下,Event对象中的信号标志(flag)被设置为假。若是有线程等待一个Event对象, 而这个Event对象的标志(flag)为假,那么这个线程将会被一直阻塞直至该

标志(flag)为真。一个线程若是将一个Event对象的信号标志(flag)设置为真,它将唤醒全部等待这个Event对象的线程。若是一个线程等待一个已经被设置为真的Event对

象,那么它将忽略这个事件, 继续执行。

Event的主要有如下几个方法:

方法 注释
set 将flag设置为“True”(设置标志)
clear 将flag设置为“False”(清除标志)
isSet 判断是否设置了flag("True" or "False")
wait 会一直监听flag,若是没有检测到flag(即:flag为"Flase")就一直处于阻塞状态

经过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程作交通指挥灯,生成几个线程作车辆,车辆行驶按红灯停,绿灯行的规则:

import threading
import time

# 定义红绿信号灯
def lighter(): 
    count = 1
    event.set()      # 初始状态设置flag,wait()不阻塞,即为绿灯
    while True:

        if 5 < count <= 10:
            event.clear()    # 将flag清除,即变红灯
            print('---red light on---')
        elif count > 10:
            event.set()     # 设置flag,即变绿灯
            count = 0
        else:
            print('---green light on---')
        count += 1
        time.sleep(1)

# 定义行驶车辆
def car():
    while True:
        if event.is_set():   # 检测flag设置,返回"True"即绿灯
            print('car is running go go go...')
            time.sleep(2)
        else:
            print('car is waiting light...')
            event.wait()    # flag被清除,等待从新设置,即红灯
            print('The green light is on, start going...')


if __name__ == '__main__':
    event = threading.Event()       # 建立一个event实例
    t = threading.Thread(target=lighter,)
    c = threading.Thread(target=car,)
    t.start()
    c.start()

2.9 队列(queue)

Queue的种类:

  • FIFO

 Queue.Queue(maxsize=0)

FIFO即First in First Out,先进先出。Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是个整数,指明了队列中能存放的数据个数的上限。一旦达到上

限,插入会致使阻塞,直到队列中的数据被消费掉。若是maxsize小于或者等于0,队列大小没有限制。

  •  LIFO

Queue.LifoQueue(maxsize=0)

LIFO即Last in First Out,后进先出。与栈的相似,使用也很简单,maxsize用法同上

  • priority

class Queue.PriorityQueue(maxsize=0)

构造一个优先队列。maxsize用法同上。

经常使用方法:

方法 注释
Queue.qsize() 返回队列的大小
Queue.empty() 若是队列为空,返回True,反之False
Queue.full() 若是队列满了,返回True,反之False
Queue.put(item,block=True, timeout=None)  写入队列,timeout等待时间
Queue.get(block=True, timeout=None) 获取队列,timeout:等待时间
Queue.put_nowait(item) 至关Queue.put(item, False)
Queue.get_nowait()  至关Queue.get(False)
Queue.task_done() 在完成一项工做以后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操

2.9.1生产者消费者模型:

在并发编程中使用生产者和消费者模式可以解决绝大多数并发问题。该模式经过平衡生产线程和消费线程的工做能力来提升程序的总体处理数据的速度。

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

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,若是生产者处理速度很快,而消费者处理速度很慢,那么

生产者就必须等待消费者处理完,才能继续生产数据。一样的道理,若是消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问

题因而引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是经过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通信,而经过阻塞队列来进行通信,因此生产者

生产完数据以后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就至关于一个缓冲区,平衡了

生产者和消费者的处理能力。

来看一个模型例子:

import threading,queue,time

count = 0

# 定义生产者
def producer(name):
    global count
    while q.qsize() < 20:    # 队列的<20时
        count += 1
        q.put(count)         # 将数据放入队列
        print('[%s] has produced %s bones...' % (name, count))
        time.sleep(0.5)
    else:            #队列达到20时,暂停生产25秒
        time.sleep(25)
        return producer(name)

# 定义消费者
def consumer(name):
    while True:  
        data = q.get()     # 获取队列赋值给data
        print('[%s] has eaten the number of %s bone...' % (name, data))
        time.sleep(3)


if __name__ == '__main__':
    q = queue.Queue()      # 实例化一个队列
    t = threading.Thread(target=producer, args=('Eric',))
    c1 = threading.Thread(target=consumer, args=('yuanyuan',))
    c2 = threading.Thread(target=consumer, args=('fangfang',))
    t.start()
    c1.start()
    c2.start()

三、多进程

multiprocessing模块就是跨平台版本的多进程模块(Unix/Linux操做系统提供了一个fork()系统调用),multiprocessing模块提供了一个Process类来表明一个进程对象,

用法和threading相似:

from multiprocessing import Process
import os, time

def run_pro(name):
    print('Run child Process: %s[%s]' % (name, os.getpid()))    # 获取进程ID


if __name__ == '__main__':
    p = Process(target=run_pro, args=('num1',))   
    print('Process will start...')
    time.sleep(1)
    print('Main Process is:',os.getpid())
    p.start()
    p.join()

3.1 进程间的通讯

Process之间确定是须要通讯的,Python的multiprocess模块提供了Queue、Pipes等多种方式来交换数据。

3.1.1 Queue

这里以Queue为例,在父进程中建立两个子进程,一个往Queue里写数据,一个从Queue里读数据(使用方法跟threading里的queue差很少)

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from multiprocessing import Queue,Process
import os

# 定义写入方法
def write(n):
    print('Process to write %s' % os.getpid())
    for i in ['X', 'Y', 'Z']:
        n.put(i)      # 将数据写入队列
        print('Put %s to queue' % i)

# 定义读取方法
def read(n):
    print('Process to read %s' % os.getpid())
    while True:
        print('Get %s to queue' % n.get())      # 从队列读取数据


if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=write, args=(q,))
    p2 = Process(target=read, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.terminate()        #读取是死循环,强制结束

执行结果以下:

Process to write 8492
Process to read 9860
Put X to queue
Get X to queue
Put Y to queue
Get Y to queue
Put Z to queue
Get Z to queue

3.1.2 pipe

Pipe的本质是进程之间的数据传递,而不是数据共享,这和socket有点像。pipe()返回两个链接对象分别表示管道的两端,每端都有send()

和recv()方法。若是两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据

from multiprocessing import Process, Pipe
import time

# 发送数据
def parent(conn):
    for i in range(5):
        conn.send(i)
        print('\033[41;1m send [%s] from parent\033[0m' % i)
        time.sleep(0.1)       # 为了更清晰看出发送和接收的状态

# 接收数据
def child(conn):
    while True:
        print('\033[46;1m child has received [%s] \033[0m' % conn.recv())


if __name__ == '__main__':
    child_pipe, parent_pipe = Pipe()     # 实例化一个管道(一个管道有两端)
    p = Process(target=parent, args=(parent_pipe,))
    c = Process(target=child, args=(child_pipe,))
    p.start()
    c.start()
    p.join()
    c.terminate()     # 强制结束

执行结果以下:

 send [0] from parent
 child has received [0] 
 send [1] from parent
 child has received [1] 
 send [2] from parent
 child has received [2] 
 send [3] from parent
 child has received [3] 
 send [4] from parent
 child has received [4] 

3.2 Manager

(Queue and Pipe objects should only be shared between processes through inheritance.)

经过Manager可实现进程间数据的共享。Manager()返回的manager对象会经过一个服务进程,来使其余进程经过代理的方式操做python对象。

manager对象支持listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValue and Array.

from multiprocessing import Manager,Process
import os


def fanc(d):
    d[os.getpid()]= os.getpid()     # 在字典中添加键值均为进程ID
    print(d)


if __name__ == '__main__':
    with Manager() as manager:   # 等同于manager = Manager()
        d = manager.dict()     # 建立一个字典,能够在多个进程之间共享和传递
        p_list = []      # 建立一个保存进程的列表,方便后面join
        for i in range(10):
            p = Process(target=fanc, args=(d,))
            p.start()
            p_list.append(p)
        for res in p_list:
            res.join()

 经过上面的设置,代码中的10个进程都可以对字典d作修改,达到数据共享的目的,执行结果以下:

{13704: 13704}
{13704: 13704, 15824: 15824}
{13704: 13704, 15824: 15824, 4460: 4460}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956, 15048: 15048}
{13704: 13704, 15824: 15824, 4460: 4460, 1292: 1292, 14252: 14252, 2716: 2716, 1752: 1752, 11956: 11956, 15048: 15048, 7028: 7028}

3.3 进程锁(进程同步)

数据输出的时候保证不一样进程的输出内容在同一块屏幕正常显示,防止数据乱序的状况。
Without using the lock output from the different processes is liable to get all mixed up.

 

3.4 进程池

因为进程启动的开销比较大,使用多进程的时候会致使大量内存空间被消耗。为了防止这种状况发生可使用进程池,(因为启动线程的

开销比较小,因此不须要线程池这种概念,多线程只会频繁得切换cpu致使系统变慢,并不会占用过多的内存空间)

进程池主要有两个方法:

apply:同步执行(串行)

apply_acync:异步执行(并行)

from multiprocessing import Process,Pool
import time,os


def foo(i):
    time.sleep(1)
    print('in the process:' % os.getpid())
    return i+100


def bar(arg):
    time.sleep(0.1)
    print('-->exec done:', arg, os.getpid())


if __name__ == '__main__':
    pool = Pool(5)     # 定义一个容量为5的进程池
    for i in range(10):     # 写入十个进程,多出的会自动挂起
        # pool.apply(func=foo, args=(i,))    # 串行方式
        pool.apply_async(func=foo, args=(i,), callback=bar)    # 并行方式,func子进程执行完后,才会执行callback(回调函数),不然callback不执行(并且callback是由父进程来执行了)

    pool.close()    # 注意这里结束时必定要先close,后join。
    pool.join()
    print('end')

对Pool对象调用join()方法会等待全部子进程执行完毕,调用join()以前必须先调用close()调用close()以后就不能继续添加新的Procsee了。

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

进程池中有可用进程为止。在上面的程序中产生了10个进程,可是只能有5同时被放入进程池,剩下的都被暂时挂起,并不占用内存空间,

等前面的五个进程执行完后,再执行剩下5个进程。

相关文章
相关标签/搜索