进程和线程

fork实现多进程

普通的方法都是调用一次,返回一次,而os的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 os
from multiprocessing import Process

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

 

通过multiprocessing提供的Pool类实现多进程

Pool可以提供指定数量的进程供用户调用,默认大小是CPU的核数。

当有新的请求提交到Pool时,如果池还没有满,就会创建一个新的进程用来执行该请求,如果池中进程数已经达到规定的量大值,请求就会等待,直到池中有进程结束,才会创建新的进程来处理它。

from multiprocessing import Pool
import os
import time
import random

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()#必须放在join之前,调用close之后就不能继续添加新的进程
    p.join()
    print 'All subprocesses done.'

进程间通信

Pipe常用在两个进程间通信,Queue用在多进程间通信

Put和Get可以进行Queue操作

Put方法可以把数据插入到队列中,有两个可选参数:blocked和timeout

如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余空间。

如果超时,会抛出Queue.Full异常。

如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。

 

Get方法可以从队列读取并且删除一个元素,有两个可选参数:blocked和timeout。

如果blocked为True(默认值),并且timeout为正值,在等待时间内没有取到任何元素,会抛出Queue.Empty异常。

如果blocked为False,分两种情况:

1、如果Queue有一个值可用,则立即返回该值

2、如果队列为空,则立即抛出Queue.Empty异常

 

 

import os
import random
import time
from multiprocessing import Process,Queue

def proc_write(q, urls):
    print 'Process %s is writing...' % os.getpid()
    for url in urls:
        q.put(url)
        print 'Put %s to queue...' % url
        time.sleep(random.random())


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__':
    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_write1.start()
    proc_write2.start()
    proc_reader.start()
    proc_write1.join()
    proc_write2.join()
    proc_reader.terminate()

Pip方法返回(conn1,conn2)代表一个管道的两个端。

Pipe方法有duplex参数,如果duplex参数为True(默认值),这个管道是全双工模式,就是conn1和conn2均可收发。

如果duplex为False,conn1只负责接收消息,conn2只负责发送消息。

send和recv方法分别是发送和接收消息的方法。

在全双工模式下,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。

例子:

import multiprocessing
import random
import time,os


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_recv(pipe):
    while True:
        print 'process (%s) rev:%s'%(os.getpid(),pipe.recv())
        time.sleep(random.random())

if __name__ == '__main__':
    pipe = multiprocessing.Pipe()
    p1 = multiprocessing.Process(target=proc_send,args=(pipe[0],['url_'+str(i) for i in  range(10)]))
    p2 = multiprocessing.Process(target=proc_recv,args=(pipe[1],))
    p1.start()
    p2.start()
    p1.join()
    p2.join()

 

多线程

优点:

1、可以把运行时间长的任务放到后台去处理

2、界面可以更吸引人

3、加快程序运行速度

4、在等待任务时,可以释放一些资源

thread是低级模块,threading是高级模块,对thread进行了封装。

 

 

用threading实现多线程

第一种方式:把一个函数传入并创建Thread实例,然后调用start方法开始执行

例子:

import random
import time, threading


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


print('%s is running...' % threading.current_thread().name)
t1 = threading.Thread(target=thread_run, name='Thread_1', args=(['url_1', 'url_2', 'url_3'],))
t2 = threading.Thread(target=thread_run, name='Thread_2', args=(['url_4', 'url_5', 'url_6'],))
t1.start()
t2.start()
t1.join()
t2.join()
print('%s ended.' % threading.current_thread().name)

结果:

MainThread is running...
Current Thread_1 is running...
Thread_1 ---->>> url_1
Current Thread_2 is running...
Thread_2 ---->>> url_4
Thread_1 ---->>> url_2
Thread_2 ---->>> url_5
Thread_2 ---->>> url_6
Thread_1 ---->>> url_3
Thread_2 ended...
Thread_1 ended...
MainThread ended.

第二种方式:直接从threading.Thread继承并创建线程类,然后重写__init__和run方法

例子:

import random
import threading
import time


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)


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)

结果:

MainThread is running...
Current Thread_1 is running...
Thread_1 ---->>> url_1
Current Thread_2 is running...
Thread_2 ---->>> url_4
Thread_2 ---->>> url_5
Thread_1 ---->>> url_2
Thread_2 ---->>> url_6
Thread_1 ---->>> url_3
Thread_2 ended.
Thread_1 ended.
MainThread ended...

 

线程同步

如果多个线程共同修改某个数据,则可能会出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。使用Thread对象的Lock和RLock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。

对于Lock对象来说,如果一个线程连续两次进行acquire操作,那么由于第一次acquire之后没有release,第二次acquire将挂起线程。这会导致Lock对象永远不会release,使得线程死锁。

RLock对象允许一个线程多次对其进行acquire操作,因为在其内部通过一个counter变量维护着线程acquire的次数。而且第一次的acquire操作必须有一个release操作与之对应,在所有的release操作完成之后,别的线程才能申请该RLock对象。

例子:

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:
                print('%s release,Number:%d' % (threading.current_thread().name, num))
                break

            num += 1
            print('%s release,Number:%d' % (threading.current_thread().name, num))
            mylock.release()


if __name__ == '__main__':
    thread1 = myThread('Thread_1')
    thread2 = myThread('Thread_2')
    thread1.start()
    thread2.start()
结果:

Thread_1 locked,Number:0
Thread_1 release,Number:1
Thread_1 locked,Number:1
Thread_1 release,Number:2
Thread_1 locked,Number:2
Thread_1 release,Number:3
Thread_1 locked,Number:3
Thread_1 release,Number:4
Thread_1 locked,Number:4
Thread_1 release,Number:4

 

全局解释器锁(GIL)

在Python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁),因此在解释执行Python代码时,会产生互斥锁来限制线程对共享资源的访问,直到解释器遇到I/O操作或者操作次数达到一定数目时才会释放GIL。由于全局解释器锁的存在,在进行多线程操作的时候,不能调用多个CPU内核,只能利用一个内核,所以在进行CPU密集型操作的时候,不推荐使用多线程,更加倾向于多进程。

那么多线程适合什么样的应用场景呢?

对于IO密集型操作,多线程可以明显提高效率,例如Python爬虫的开发,绝大多数时间爬虫是在等待socket返回数据,网络IO的操作延时比CPU大得多。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值