关于多进程与多线程的概念第一个接触是在学习C语言的时候,当时借助stevens的《UNIX环境高级编程》了解了关于进程和线程的相关知识,为现在接触python的同类概念打下了基础。有兴趣的同学也可以阅读这本书。
当我们需要并行的执行多个任务的时候,在python语言中有两种方式:进程分支和线程派生。
在这节中我们将会介绍进程分支的技术,分支的进程是构建平行任务的传统做法,分支的想法基于程序的复制:在最初的阶段,当程序调用分支去开启另外一个程序的时候,操作系统会创建该程序及其在内存中的副本。但是在随后的使用过程中发现,新开启的程序往往执行的任务与原程序没有任何关系,所以操作系统提出了写时复制这样的机制(即只有在原程序的资源被使用的时候才进行拷贝),极大的提高了内存和时间的效率。
在进行了分支操作后,原来的程序副本叫做父进程,而由os.fork创建的副本称为子进程。
下面给出一个多进程的示例:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import os
def create_child():
print ('hello from child', os.getpid())
os._exit(0)
if __name__ == '__main__':
while True:
new_pid = os.fork()
if new_pid == 0:
create_child()
else:
print ('hello from parent', os.getpid(), new_pid)
if raw_input() == 'q':
break
注意:
1.使用os.fork()可以产生分支进程,返回值有两个,如果new_pid为0,则该区域的操作是属于子进程的,如果大于0则是属于父进程的操作。
这个os.fork只是对系统代码库的简单封装。
在上述示例中,每个进程运行后很快结束,因此在时间的重叠很少,接下来的示例中我们让进程进行一定时间上的sleep;
#!/usr/bin/env python
#-*-coding:utf-8-*-
import os
import time
def thild_count(count):
for i in range(count):
time.sleep(1)
print('[%s] => %s' % (os.getpid(), i))
if __name__ == '__main__':
for i in range(5):
pid = os.fork()
if pid != 0:
print('Process %d spawned' % pid)
else :
thild_count(5)
os._exit(0)
time.sleep(10)
print('Main process exiting.!')
一般来讲,fork和exec是组合使用的,实现的功能是开启一个新的进程,并且指定其完成新的任务。
如下所示:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import os
import sys
parm = 0
while True:
parm += 1
pid = os.fork()
if pid == 0:
os.execlp('python', 'python', 'fork1.py', str(parm))
assert False, 'error starting program'
else:
print ('Child is', pid)
if raw_input() == 'q':
break
exec是一组函数的统称,更多的操作如下所示:
os.execv(program, commandlinesequence)
os.execl(program, cmdarg1, cmdarg2, …cmdargN)
os.execlp
os.execvp
os.execle
os.execve
os.execvpe
除了使用fork和exec的组合外,我们还可以使用os.system、os.popen和subprocess模块的方式派生shell命令.
线程是开启另外一个任务的另一种方法,他们和程序其他部分并行地调用函数,有时候线程被称做“轻量级进程”,但是他们只能在一个进程中执行。关于进程和线程之间地关系如下所示:
1.性能改善:
因为所有地线程都是在同一个进程中进行,所以他们一般不会因为复制进程而造成其他地开销。
2.简单易用:
生去了进程间通信等复杂细节,量级轻。
3.共享全局内存:
同一进程中的线程之间可以共享全局的资源,但是需要注意同步的问题。
4.可移植性
python的线程工具自动适应平台特用的线程方面的差异,具有一致性接口。
thread模块编写的多线程程序更像是面向过程的操作,我们给出如下几个示例;
1.开启多个线程,每个线程打印自己是第几个线程:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import thread
def child(tid):
print ('Hello from thread', tid)
if __name__ == '__main__':
i = 0
while True:
i += 1
thread.start_new_thread(child, (i,))
if raw_input() == 'q':
break
2.下面我们展示多线程共同抢占stdout标准输出资源的示例,因为stdout也是该程序的公共资源,多个线程同时进行输出,可能会造成显示上的无规则:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import thread
import time
def counter(my_id, count):
for i in range(count):
time.sleep(1)
print('[%s] => %s' % (my_id, i))
if __name__ == '__main__':
for i in range(5):
thread.start_new_thread(counter, (i, 5))
time.sleep(6)
print ('the main thread exiting...')
显然上述的方案并不能让人满意,我们更希望它能进行一种同步的操作(即当一个线程正在操作stdout时,其他的线程被阻塞掉),代码如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import thread
import time
import mutex
mutex = thread.allocate_lock()
def counter(my_id, count):
for i in range(count):
time.sleep(1)
mutex.acquire()
print('[%s] => %s' % (my_id, i))
mutex.release()
if __name__ == '__main__':
for i in range(5):
thread.start_new_thread(counter, (i, 5))
time.sleep(6)
这里我们采用了线程锁的方案,当一个线程对stdout加锁后操作时,其他的线程就会被阻塞掉,直到这个线程操作结束释放锁才能进行响应。这是同步的机制。
除了避免打印上的冲突,线程模块锁还可以作为信号机这样较高层次的同步化范式。
我们使用线程锁组开判断各个子线程是否运行结束,如果结束我们结束主线程:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import thread
stdout_mutex = thread.allocate_lock()
exit_mutexes = [False] * 10
def counter(my_id, count):
for i in range(count):
stdout_mutex.acquire()
print('[%s] => %s' % (my_id, i))
stdout_mutex.release()
exit_mutexes[my_id] = True
if __name__ == '__main__':
for i in range(10):
thread.start_new_thread(counter, (i, 20))
while False in exit_mutexes:
pass
print ('the main thread exiting...')
我们还可以通过使用with语句确保在一端嵌套代码块周围执行线程操作,线程锁的上下文管理器在为with语句这一行加锁,然后在语句结束后释放锁,无论是否发生了异常。这是一种很好的保护机制。代码示例如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
import thread
import time
num_thread = 5
stdout_mutex = thread.allocate_lock()
exit_mutexes = [thread.allocate_lock() for i in range(num_thread)]
def counter(my_id, count, stdout_mutex):
for i in range(count):
time.sleep(1 / (my_id + 1))
with stdout_mutex:
print('[%s] => %s' % (my_id, i))
exit_mutexes[my_id].acquire()
if __name__ == '__main__':
for i in range(num_thread):
thread.start_new_thread(counter, (i, 10, stdout_mutex))
while not all(mutex.locked() for mutex in exit_mutexes):
time.sleep(0.25)
print ('the main thread is exiting...')
本章中我们简单的介绍了python的多进程编程以及使用thread模块进行的多线程编程,在下一章节中将会介绍threading模块进行多线程编程,它的操作是较高层次的封装,即使用类和对象对线程进行操作。