python-了解进程,线程,协程

进程

Python实现多进程的方式主要有两种,一种方法是使用os模块中fork方法,另一种方法是使用multiprocessing模块。
fork 适用于Unix/Linux操作系统,对Windows不支持
multiprocessing 支持跨平台

使用os模块中的fork方法实现多进程
Python的os模块封装了常见的系统调用,其中有fork方法。fork方法来自于Unix/Linux操作系统中提供的一个fork系统调用。
这个方法是调用一次,返回两次,因为系统将当前进程(父进程)复制一份进程(子进程),两个进程几乎完全相同,于是fork方法分别再父进程和子进程中返回。
子进程永远返回0,父进程中返回的子进程的ID。

import os
if __name__ == '__main__':
    print('current Process (%s) start ...' % (os.getpid()))
    pid = os.fork()
    if pid < 0:
        print('error in fork')
    elif pid == 0:
        print('I am child Process(%s) and my parent process is(%s)', (os.getpid()), (os.getppid()))
    else:
        print('I (%s) created a child process (%s).', (os.getpid()), pid)

使用multiprocessing模块创建多进程
multiprocessing模块提供了一个Process类来描述一个进程对象。创建子进程,只需要传入一个执行函数和函数的参数,即可完成一个Process实例的创建,用start()方法启动进程,用join()方法实现进程间同步。

import multiprocessing
def run_proc(name):
    print('Child process %s (%s) Running...' % (name, os.getpid()))


if __name__ == '__main__':
    print('Parent process %s.' % os.getpid())
    for i in range(5):
        p = Process(target=run_proc, args=(str(i),))
        print('Process will start')
        p.start()
    p.join()
    print('Process end.')

以上创建进程的两种方法,如果只是需要少量的进程,可以直接手工创建,如果需要大量的进程,就需要进程池Poll发挥作用。
Pool对象调用 join() 方法会等待所有子进程执行完毕,调用 join() 之前必须先调用close(), 调用close()之后就不能继续添加新的Process了。

import os, time, random
from multiprocessing import Pool
def run_task(name):
    print('Task %s (pid = %s) is running...' % (name, os.getpid()))
    time.sleep(random.random() * 3)
    print('Task %s end.' % name)


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

进程间通信
如果创建大量的进程,那么进程间通信是必不可少的。
Python提供了多种进程间通信的方式,例如Queue, Pipe, Value+Array等。
Queue 用来多个进程间实现通信
Pipe 常用再两个进程间通信

Queue通信方式,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。Put和Get可以进行操作。
Put方法用以插入数据到队列中,有两个参数:blocked和timeout。
如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的事件,直到该队列有剩余的空间。
如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
Get方法可以从队列读取并删除一个元素,有两个参数:blocked和timeout
如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。
如果blocked为False,如果Queue有一个值可用,则立即返回该值;否则,如果队列为空,则立即抛出Queue.Empty异常。

import os, time, random
from multiprocessing import Process, Queue
def proc_read(q):
    print('Process(%s) is reading...' % os.getpid())
    while True:
        url = q.get(True)
        print('Get %s from queue' % url)


if __name__ == '__main__':
    # 父进程创建Queue,并传给各个子进程
    q = Queue()
    proc_write1 = Process(target=proc_write, args=(q, ['url_1', 'url_2', 'url_3']))
    proc_write2 = Process(target=proc_write, args=(q, ['url_4', 'url_5', 'url_6']))
    proc_reader = Process(target=proc_read, args=(q,))

    # 启动子进程proc_writer 写入
    proc_write1.start()
    proc_write2.start()
    # 启动子进程proc_reader 读取
    proc_reader.start()
    # 等待 proc_writer结束
    proc_write1.join()
    proc_write2.join()
    # proc_reader进程里是死循环,无法等待其结束,只能强行终止
    proc_reader.terminate()

Pipe通信机制,Pipe常用来在两个进程间进行通信,两个进程分别位于管道的两端。
Pipe方法返回(conn1, conn2)代表一个管道的两个端。Pipe方法有duplex参数。
如果duplex参数为True(默认值),那么这个管道是全双工模式,也就是说conn1 和 conn2均可收发。
若duplex为False,conn1值负责接受消息,conn2值负责发送信息。
send和recv方法分别是发送和接受信息的方法。
如果没有消息可接收,recv方法会一致阻塞。如果管道已经被关闭,那么recv方法会抛出错误

多线程

多线程类似于同时执行多个不同程序,多线程运行有如下优点:
可以把运行时间长的任务放到后台区处理
程序的运行速度可能加快
一些需要等待的任务上,线程就比较有用。
Python的标准库提供了两个模块:thread 和 threading, 主要用threading

threading模块创建多线程
1.把一个函数传入并创建Thread实例,然后调用start方法。
2.直接从threading.Thread继续并创建线程类,然后重写__init__方法和run方法。

import time, random
import threading
def proc_send(pipe, urls):
    for url in urls:
        print('Process(%s) send: %s' %(os.getpid(), url))
        pipe.send(url)
        time.sleep(random.random())


def proc_rcov(pipe):
    while True:
        print('Process(%s) rev:%s' %(os.getpid(), pipe.recv()))
        time.sleep(random.random())


if __name__ == '__main__':
    pipe = multiprocessing.Pipe()
    p1 = Process(target=proc_send, args=(pipe[0], ['url_' + str(i) for i in range(10)]))
    p2 = Process(target=proc_rcov, args=(pipe[1],))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
class myThread(threading.Thread):
    def __init__(self, name, urls):
        threading.Thread.__init__(self, name=name)
        self.urls = urls

    def run(self):
        print('Current %s is running...' % threading.current_thread().name)
        for url in self.urls:
            print('%s  --->>> %s' % (threading.current_thread().name, url))
            time.sleep(random.random())
        print('%s ended.' % threading.current_thread().name)


if __name__ == '__main__':
    print('%s is running...' % threading.current_thread().name)
    t1 = myThread(name='Thread_1', urls=['url_1', 'url_2', 'url_3'])
    t2 = myThread(name='Thread_2', urls=['url_4', 'url_5', 'url_6'])
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('%s ended.' % threading.current_thread().name)

线程同步
如果多个线程共同对某个数据修改,为了保证数据的正确性,需要对多个线程进行同步。
使用Thread对象的Lock和RLock可以实现简单的线程同步。
这个两个对象都有acquire方法和release方法。
Lock对象,如果一个线程连续两次进行acquire操作,那么由于第一次acquire之后没有release,第二次acquire将挂起线程。这会使线程死锁。
RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护这线程acquire的次数。每一次的acquire操作必须有一个release操作与之对应。

import threading
myLock = threading.RLock()
num = 0

class myThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self, name=name)

    def run(self):
        global num
        while True:
            myLock.acquire()
            print('%s locked, Number: %d' % (threading.current_thread().name, num))
            if num >= 4:
                myLock.release()
                print('%s released, Number: %d' % (threading.current_thread().name, num))
                break
            num += 1
            print('%s released, Number: %d' % (threading.current_thread().name, num))
            myLock.release()


if __name__ == '__main__':
    thread1 = myThread('Thread_1')
    thread2 = myThread('Thread_2')
    thread1.start()
    thread2.start()
协程

又称微线程,是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,切换回来时候就恢复先前保存的寄存器上下文和栈。
因此协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
在并发中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源.
协程需要用户自己来编写调度逻辑,对于CPU来说,协程其实时单元程,所以CPU不用区考虑怎么调度,切换上下文,省去了CPU的切换开销,所以协程在一定程度上又好于多线程。
Python通过yield提供了对协程的基本支持,但是不完全,而使用第三方gevent提供了比较完善的协程支持。
greenlet在libev事件循环顶部提供了一个有高级别并发性的API:
特征:
基于libev的快速事件循环,Linux上时epoll机制。
基于greenlet的轻量级执行单元
API复用了Python标准库里的内容。
支持SSL的协作式sockets。
可通过线程池或 c-ares实现DNS查询
通过monkey patching功能使得第三方模块变成协作式。

gevent对协程的支持,本质上是greenlet的实现切换工作。
greenlet工作流程如下:
加入进行访问网络的IO操作时,出现阻塞,greenlet就显式切换到另一段没有被阻塞的代码段执行,知道原先的阻塞状态消失以后,再自动切换回原来的代码段继续处理。greenlet式一种合理安排的串行方式。
由于IO操作非常耗时,而不是等待IO,这就是协程一般比多线程效率高的原因。由于切换是再IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,将一些常见的阻塞,如socket,select等地方实现协程跳转,
这一过程在启动时通过monkey patch完成。

import gevent
from gevent.pool import Pool
import urllib3

def run_task(url):
    print('Visit -- > %s' % url)
    try:
        http = urllib3.PoolManager()
        response = http.request('GET', url)
        data = response.data
        print('%d bytes received from %s.' % (len(data), url))
    except Exception as e:
        print(e)


if __name__ == '__main__':
    urls = ['https://github.com/', 'https://python.org/', 'http://www.cnblogs.com/']
    greenlets = [gevent.spawn(run_task, url) for url in urls]
    gevent.joinall(greenlets)

主要用了gevent和spawn方法和joinall方法。spawn方法可以看做是用来形成协程,joinall方法就是添加这些协程任务,并且启动运行。从运行结果来看,3个网络操作是并发执行的,而且顺序不同,但其实只有一个线程。
gevent中还提供了对池的支持,当拥有动态数量的greenlet需要进行并发管理(限制并发数)时,就可以使用池,这在处理大量的网络和IO操作时是非常需要的。

import gevent
from gevent.pool import Pool
import urllib3
def run_task(url):
    print('Visit --> %s' % url)
    try:
        http = urllib3.PoolManager()
        response = http.request('GET', url)
        data = response.data
        print('%d bytes received from %s.' % (len(data), url))
    except Exception as e:
        print(e)
    return 'url:%s ---->finish' % url


if __name__ == '__main__':
    pool = Pool(2)
    urls = ['https://github.com/', 'https://python.org/', 'http://www.cnblogs.com/']
    results = pool.map(run_task, urls)
    print(results)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值