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大得多。