3.python学习笔记:python下的多进程与多线程(一)

python下的多进程与多线程

python多进程

关于多进程与多线程的概念第一个接触是在学习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.systemos.popensubprocess模块的方式派生shell命令.

python多线程

线程与进程

线程是开启另外一个任务的另一种方法,他们和程序其他部分并行地调用函数,有时候线程被称做“轻量级进程”,但是他们只能在一个进程中执行。关于进程和线程之间地关系如下所示:

1.性能改善:
因为所有地线程都是在同一个进程中进行,所以他们一般不会因为复制进程而造成其他地开销。

2.简单易用:
生去了进程间通信等复杂细节,量级轻。

3.共享全局内存:
同一进程中的线程之间可以共享全局的资源,但是需要注意同步的问题。

4.可移植性
python的线程工具自动适应平台特用的线程方面的差异,具有一致性接口。

thread模块

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模块进行多线程编程,它的操作是较高层次的封装,即使用类和对象对线程进行操作。

你可能感兴趣的:(多线程,python,unix)