线程与进程详解(Python编程)

在项目开发中,无论是提高程序运行效率还是打造分布式编程,都离不开线程与进程的身影。使用多进程、多线程可以提高程序处理的效率。

多进程

在Python中,创建多进程通常是使用 multiprocessing模块。在multiprocessing中,提供了一个Process类来描述一个进程对象,通过这一个类即可完成对进程的创建。

import os
from multiprocessing import Process


def run(id):
    print('Child process %s / %s is running...' % (id, os.getpid()))


if __name__ == '__main__':
    for i in range(5):
        p = Process(target = run, args = (str(i), ))
        print('now is starting...')
        p.start()
    p.join()
    print('Process  end...')

但是无论是多进程还是多线程,运行的顺序都不是按照程序执行编写的顺序执行,这个之后会讲解。

倘若我们需要创建指定数量的进程给用户,就可以使用Pool类。Pool可以提供用户指定数量的进程,通过Pool,就相当于创建一个池,池中放置一定数量的进程,当用户需要使用的时候,就往里头调取就可以了。

from multiprocessing import Pool
import  os
import random
import time

def run(id):
    print('Task  %s (pid = %s )is running' % (id ,os.getpid()))
    time.sleep(random.random() * 3)
    print('Task %s end' % id)

if __name__ == '__main__':
    print('Current process %s' %os.getpid())
    p = Pool(processes = 5)
    for i in range(10):
        p.apply_async(run, args = (i,),)
    print('waiting done...')
    p.close()
    p.join()
    print('all done...')

当我们创建了多个进程时,那么它们之间的通信就必不可少。首先我们介绍Queue队列,它是多进程安全的队列,队列拥有先进先出的特点。我们可以通过使用Put,Get对Queue进行操作。它们都有两个可选参数:timeout,即方法要等待的时间;以及blocked,这个是阻塞的标志位。当blocked为True,并且timeout大于零时,put或者get方法就会阻塞一定时间,直到队列有剩余的空间,否则当没有空间剩余的时候就会抛出Queue.Full异常。

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

def writeProcess(q, tasks):
    print('Process %s is writing...' % os.getpid())
    for t in tasks:
        q.put(t)
        print('Put %s to queue...' % os.getpid())
        time.sleep(random.randint(1, 5))

def readProcess(q):
    print('Process %s is reading...' % os.getpid())
    while True:
        t = q.get(True)
        print('get %s from queue' % os.getpid())


if __name__ == '__main__':
    q = Queue()
    proc_write1 = Process(target = writeProcess, args = (q,['t1', 't2', 't3']))
    proc_write2 = Process(target=writeProcess, args=(q, ['t4', 't5', 't6']))
    proc_read = Process(target = readProcess, args = (q,))
    proc_write1.start()
    proc_write2.start()
    proc_read.start()
    proc_write1.join()
    proc_write2.join()
    proc_read.terminate()#强制停止

程序最后为什么会使用到terminate()这个方法呢,因为读取的函数是一个死循环函数,无法自动停止,所以只能通过这个函数来强制停止。

以上程序另外提及random的用法:

random.random
用于生成一个0到1的随机符点数: 0 <= n < 1.0

random.uniform
该函数的原型为random.uniform(a, b),用于生成一个指定范围内的随机符点数,两个参数其中一个是上限,一个是下限。如果a > b,则生成的随机数n: a <= n <= b。如果 a <b, 则 b <= n <= a。

random.randint(a, b),同上面原型参数一样,用于生成一个指定范围内的整数。其中参数a是下限,参数b是上限,生成的随机数n: a <= n <= b

random.choice从序列中获取一个随机元素。其函数原型为:random.choice(sequence)。参数sequence表示一个有序类型,比如列表,元组,字符串。

介绍完队列通信机制后现在来介绍管道通信。管道是双通道通信机制,双通道即两端都可以进行读或者写操作。Pipe方法中有duplex参数,它是来控制管道的两侧是均可进行读写还是一侧只负责接收消息,另一侧只负责发送消息。当duplex为False时,就是一侧只负责接收消息,另一侧只负责发送消息。

import multiprocessing
import random
import time
import os

def proc_send(pipe, names):
    for name in names:
        print('Process %s send %s:' %(os.getpid(), name))
        pipe.send(name)
        time.sleep(random.uniform(1,5))

def proc_recv(pipe):
    while True:
        print('Process %s recv : %s' %(os.getpid(), pipe.recv()))
        time.sleep(random.uniform(1, 5))

if __name__ == '__main__':
    pipe = multiprocessing.Pipe()
    p1 = multiprocessing.Process(target = proc_send, args = (pipe[0], ['one', 'two', 'three']))
    p2 = multiprocessing.Process(target = proc_recv, args = (pipe[1],))
    p1.start()
    p2.start()
    p1.join()
    p2.terminate()

多线程

类似多进程,在Python中,一般是通过继承threading.Thread这个类来创建我们所需要的线程类。

多线程就类似同时执行多个不同的程序,通过多线程,可以把运行时间长的任务放到后台处理;同时,在一些需要等待的任务上,我们可以释放内存占用这一些珍贵的资源。比如Io操作,网络收发数据。

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 loscked, number: %d' % (threading.current_thread().name, num))
            if num > 4:
                mylock.release()
                print('%s release, number : %d' % (threading.current_thread().name, num))
                break
            num += 1
            mylock.release()
            print('%s release, number : %d' % (threading.current_thread().name, num))


if __name__ == '__main__':
    t1 = myThread('Thread1')
    t2 = myThread('Thread2')
    t1.start()
    t2.start()

在这里是我们使用锁来对线程进行线程同步操作。如果多个线程同时操作一个数据时,不同步的话则可能出现不可预料的结果。在Lock中,有acquire和release两个方法,一个acquire对应一个release。当一个线程同时进行两个acquire操作时,因为第一次的acquire没有释放掉,所以无法进行第二次的acquire。而RLock对象允许一个线程同时进行多次acquire,而count这个数目在创建RLock时就要确认好。但是要注意的是,每一次的acquire都对应着一个release,,在所有的release完成后,别的线程才可以申请这个RLock对象。


最后介绍一下进程的死锁

死锁的特征:

当发生死锁时,那么进程永远不能完成当前的任务,那么系统的资源就会被阻碍使用,以致于阻止了其他作业开始执行。

死锁发生的必要条件,我们强调所有四个条件必须同时成立才会出现死锁

如果在一个系统中以下四个条件同时成立,那么就能引起死锁:

  • 互斥:至少有一个资源必须处于非共享模式,即一次只有一个进程可使用。如果另一进程申请该资源,那么申请进程应等到该资源释放为止。
  • 占有并等待:—个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
  • 非抢占:资源不能被抢占,即资源只能被进程在完成任务后自愿释放。
  • 循环等待:有一组等待进程 {P0,P1,…,Pn},P0 等待的资源为 P1 占有,P1 等待的资源为 P2 占有,……,Pn-1 等待的资源为 Pn 占有,Pn 等待的资源为 P0 占有。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值