多线程、多进程、线程池、进程池

多任务

不管是单核CPU还是多核CPU,一旦任务数量超过核数,OS都会把每个任务轮流调度到每个核心上。OS实现多进程和多线程往往是通过时间片的形式执行的,即让每个任务(进程/线程)轮流交替执行,因为时间片切分的很小,以至于我们感觉多个任务在同时执行。

如果我们要同时执行多个任务怎么办?

主要有两种解决方案:

  • 一种是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。

  • 还有一种方法是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。

  • 当然还有第三种方法,就是启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。

总结一下就是,多任务的实现有3种方式:

多进程模式
多线程模式
多进程+多线程模式

同时执行多个任务通常各个任务之间并不是没有关联的,而是需要相互通信和协调,有时,任务1必须暂停等待任务2完成后才能继续执行,有时,任务3和任务4又不能同时执行,所以,多进程和多线程的程序的复杂度要远远高于单进程单线程的程序。

线程是最小的执行单元,而进程由至少一个线程组成。

如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

多线程

一个进程内可以有多个线程(至少一个主线程),多个线程共享该进程的所有变量,同时对全局变量进行访问和改写很容易出现混乱,所以需要用锁进行线程的同步控制。python由于设计时有GIL全局锁,所以多线程无法利用多核CPU,也不存在多线程并发的问题。

Python标准库中提供了_thread和threading两个模块来实现多线程,threading是_thread的高级封装,因此一般只需要import threading即可。

import time, threading

def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n+1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s is end.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s is end.' % threading.current_thread().name)

#output
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread is end.
thread MainThread is end.

其实,多线程和多进程的实现很类似,进程启动时默认的线程为主线程,主线程之外可以开启新的子线程,current_thread()函数返回当前线程的实例。

多进程

Linux/Unix操作系统提供了fork()系统调用,fork执行一次会返回两次,分别在父进程和子进程内返回(先父进程再子进程),父进程返回的是子进程的ID,子进程返回0。父进程要记下所有子进程的ID,而子进程可以通过getppid()获得父进的ID。

在python中,可以通过from mutilprocessing import Process来创建进程类对象:

p = Process(target = run_proc, args = (‘args1’,…))

其中,run_proc是子进程函数,args是传入子进程函数的参数,用tuple格式传递.

  • p.start()–开始执行子进程(函数)
  • p.join()–用来等待该进程结束,用于进程同步
    举个例子:

from multiprocessing import Process
import os

def run_proc(name):
    print 'Run child process %s (%s)' % (name, os.getpid())

if __name__ == '__main__':
    print 'Parent process %s.' % os.getpid()
    p1 = Process(target=run_proc, args=('test1',))
    p2 = Process(target=run_proc, args=('test2',))
    p3 = Process(target=run_proc, args=('test3',))
    p4 = Process(target=run_proc, args=('test4',))
    print 'Process will start.'
    p1.start()
    p2.start()
    p3.start()
    p4.start()
    p1.join()
    p2.join()
    p3.join()
    p4.join()

在主进程中创建了4个子进程,但是子进程的创建顺序是由OS决定的,并不是按照程序中的顺序执行。

Parent process 21410.
Process will start.
Run child process test4 (24745)
Run child process test1 (24742)
Run child process test2 (24743)
Run child process test3 (24744)

Parent process 21410.
Process will start.
Run child process test1 (24754)
Run child process test4 (24759)
Run child process test2 (24755)
Run child process test3 (24756)

进程池和线程池

进程池和线程池性质相似,下面以线程池为例进行说明。

既然是“池”,我们很容易联想到“水池”,在使用水的过程中,水池起到了一个缓冲的作用,避免了频繁开关水龙头。同理,进程池也是通过事先划分一块系统资源区域,这组资源区域在服务器启动时就已经创建和初始化,用户如果想创建新的进程,可以直接取得资源,从而避免了动态分配资源(这是很耗时的)。

线程池内子进程的数目一般在3~10个之间,子线程都运行着相同的代码,并具有相同的属性,如优先级,PGID等。
当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:

  • 1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。

  • 2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。

当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。

最后用代码实现一下进程池:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
    print 'Run task %s (%s)...' % (name, os.getpid())
    start = time.time()
    time.sleep(random.random()*3)
    end = time.time()
    print 'Task %s runs %0.2f seconds.' % (name, end-start)

if __name__ == '__main__':
    print 'Parent process %s.' % os.getpid()
    p = Pool()
    for i in range(5):
        p.apply_async(long_time_task, (i,))
    print 'Waiting for all subprocesses done...'
    p.close()
    p.join()
    print 'All subprocesses done.'

#ouput
Parent process 21410.
Run task 2 (25238)...
Run task 1 (25235)...
Run task 3 (25237)...
Run task 0 (25233)...
Waiting for all subprocesses done...
Task 1 runs 0.36 seconds.
Run task 4 (25235)...
Task 0 runs 0.91 seconds.
Task 3 runs 1.05 seconds.
Task 2 runs 2.78 seconds.
Task 4 runs 2.72 seconds.
All subprocesses done.

python中Pool对象中默认子进程数量为4,因此任务4要等到进程池中某个子进程执行完后才能执行。当然可以自己设置Pool中子进程的数目。对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞的–异步
  • apply(func[, args[, kwds]])是阻塞的-同步

内存池

既然说了进程池和线程池,也不妨再了解下内存池,内存池和进程池的工作方式也很类似,都是以空间换取时间。

内存池是一种内存分配方式。通常我们习惯直接使用new、malloc等系统调用申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

ref:
1.http://blog.csdn.net/u011012049/article/details/48436427
2.廖雪峰的官方网站

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值