一切皆有可能
anywill ,anything will ....go better or worse ,go success or failure,go......一切皆有可能

进程、线程与协程细说

anywill~2018-05-28 /Linux编程

0x01 进程

平时写代码,进程是3个“程”听的最多的,也是最“古老”的。

进程是一个程序在一个数据集中的一次动态执行过程,可以简单理解为“正在执行的程序”,它是CPU资源分配和调度的独立单位。
进程一般由程序数据集进程控制块三部分组成。
程序用来描述进程要完成哪些功能以及如何完成;
数据集则是程序在执行过程中所需要使用的资源;
进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

进程的局限是创建、撤销和切换的开销比较大。
线程协程都是围绕这块优化而产生。

为了更好引入线程这个东西,先来一个例子:

队列是开发中常用到的,一般是有生产者和消费者组成,现在我们用多进程实现。

文件名为:mutilProcess.py

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

from multiprocessing import Process, Queue
import os
import time


def write(q):
    for value in ['A', 'B', 'C']:
        print 'Put %s to queue...' % value
        q.put(value)
        time.sleep(3)


def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print 'Get %s from queue.' % value
            time.sleep(3)
        else:
            break


if __name__ == '__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, name="test_pw", args=(q,))
    pr = Process(target=read, name="test_pr", args=(q,))
    pw.start()
    pw.join()
    pr.start()
    pr.join()
    print
    print '所有数据都写入并且读完'

 

这时,用htop命令,按下”F4″,输入过滤内容”mutilProcess”,可以看到2个pid不同的进程在跑,一个是父进程,一个子进程。

为了减少进程间切换带来的消耗,我们把2个功能“放”到一个进程内,一个功能就是一个“线程”。

用学习来打比方,假设做语文和数学2门课,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换回来。

但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。

现在,我们把“语文”和“数学”都放在一本书上,当你切换时,只要翻到“数学”或者“语文”页就可以了,不用再重新合上书本,收拾书本,拿出新书本,再打开书本。

0x02 线程

线程是在进程之后发展出来的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。一个进程可以包含多个线程。
线程的优点是减小了程序并发执行时的开销,提高了操作系统的并发性能,缺点是线程没有自己的系统资源,只拥有在运行时必不可少的资源,但同一进程的各线程可以共享进程所拥有的系统资源,如果把进程比作一个车间,那么线程就好比是车间里面的工人。不过对于某些独占性资源存在锁机制,处理不当可能会产生“死锁”。

现在,我们用多线程完成上面的队列例子。

文件名:mutilThread.py

from Queue import Queue
import threading
import time


def write(q):
    for value in range(20):
        print 'Put %s to queue...' % value
        q.put(value)
        time.sleep(1)
    time.sleep(1)
    print "write done"


def read(q):
    i = 0
    while True:
        if not q.empty():
            value = q.get(True)
            print 'Get %s from queue.' % value
            q.task_done()


if __name__ == '__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()

    tr = threading.Thread(target=read, name="mutil_test_tr", args=(q,))
    tw = threading.Thread(target=write, name="mutil_test_tw", args=(q,))
    tr.start()
    tw.start()

    q.join()
    print '所有数据都写入并且读完'

从htop可以看到,这时,只有一个进程在跑。

从系统看,假如系统分配4ms给这个进程,2ms跑write线程,2ms跑read线程。

注意,在python中,因为GIL机制,一个进程多个线程里,当且仅有一个线程在跑。所以,多线程无法利用多核CPU。

如果要利用多核,那就先多开进程 ,然后在单个进程多开线程。

0x03 协程

协程,又称微线程,纤程,函数级别。英文名Coroutine。

如果用上面学习的例子来说,“线程”就是页面级别的切换,而“协程”就是行级的切换,更加省力,省心。

先上代码:

import time


def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        time.sleep(1)
        r = '200 OK'


def produce(c):
    c.next()
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()


if __name__ == '__main__':
    c = consumer()
    produce(c)

同样的队列功能,之前要2个线程才能实现,现在用1个线程内2个函数就可以实现。

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

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


发表评论

电子邮件地址不会被公开。 必填项已用*标注