文章内容参考自廖雪峰python、传智播客python教程等
进程
进程的创建
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
说明:
1.multiprocessing 是跨平台模块
2.os.getpid()用于获取当前进程标识
3.join()方法需要等待子线程结束后再往下执行,可以理解为子线程“插队”到主线程前执行
实例1:
from multiprocessing import Process
import os
from time import sleep
def run_proc(name, age, **kwargs):
for i in range(100):
print(i)
print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
sleep(2)
print(kwargs)
if __name__ == '__main__':
print('父进程 %d.' % os.getpid())
p = Process(target=run_proc, args=('test', 18), kwargs={"m": 20})
print('子进程将要执行')
p.start()
sleep(1)
# 进程提前结束
p.terminate()
p.join()
print('子进程已结束')
实例2:
# coding=utf-8
from multiprocessing import Process
import time
import os
# 两个⼦进程将会调⽤的两个⽅法
def worker_1(interval):
print("worker_1,⽗进程(%s),当前进程(%s)" % (os.getppid(), os.getpid()))
t_start = time.time()
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time()
print("worker_1,执⾏时间为'%0.2f'秒" % (t_end - t_start))
def worker_2(interval):
print("worker_2,⽗进程(%s),当前进程(%s)" % (os.getppid(), os.getpid()))
t_start = time.time()
time.sleep(interval)
t_end = time.time()
print("worker_2,执⾏时间为'%0.2f'秒" % (t_end - t_start))
if __name__ == "__main__":
# 输出当前程序的ID
print("进程ID:%s" % os.getpid())
# 创建两个进程对象,target指向这个进程对象要执⾏的对象名称,
# args后⾯的元组中,是要传递给worker_1⽅法的参数,
# 因为worker_1⽅法就⼀个interval参数,这⾥传递⼀个整数2给它,
# 如果不指定name参数,默认的进程对象名称为Process-N,N为⼀个递增的整数
p1 = Process(target=worker_1, args=(2,))
p2 = Process(target=worker_2, name="dongGe", args=(1,))
# 使⽤"进程对象名称.start()"来创建并执⾏⼀个⼦进程,
# 这两个进程对象在start后,就会分别去执⾏worker_1和worker_2⽅法中的内容
p1.start()
p2.start()
# 同时⽗进程仍然往下执⾏,如果p2进程还在执⾏,将会返回True
print("p2.is_alive=%s" % p2.is_alive())
# 输出p1和p2进程的别名和pid
print("p1.name=%s" % p1.name)
print("p1.pid=%s" % p1.pid)
print("p2.name=%s" % p2.name)
print("p2.pid=%s" % p2.pid)
# join括号中不携带参数,表示⽗进程在这个位置要等待p1进程执⾏完成后,
# 再继续执⾏下⾯的语句,⼀般⽤于进程间的数据同步,如果不写这⼀句,
# 下⾯的is_alive判断将会是True,在shell(cmd)⾥⾯调⽤这个程序时
# 可以完整的看到这个过程,⼤家可以尝试着将下⾯的这条语句改成p1.join(1),
# 因为p2需要2秒以上才可能执⾏完成,⽗进程等待1秒很可能不能让p1完全执⾏完成,
# 所以下⾯的print会输出True,即p1仍然在执⾏
p1.join()
print("p1.is_alive=%s" % p1.is_alive())
实例3:
from multiprocessing import Process
import time
import os
# 继承Process类
class ProcessClass(Process):
# 因为Process类本身也有__init__⽅法,这个⼦类相当于重写了这个⽅法,
# 但这样就会带来⼀个问题,我们并没有完全的初始化⼀个Process类,所以就不能使⽤从这个类继承的⼀些⽅法和属性,
# 最好的⽅法就是将继承类本身传递给Process.__init__⽅法,完成这些初始化操作
def __init__(self, interval):
Process.__init__(self)
self.interval = interval
# 重写了Process类的run()⽅法
def run(self):
print("⼦进程(%s) 开始执⾏,⽗进程为(%s)" % (os.getpid(), os.getppid()))
t1_start = time.time()
time.sleep(self.interval)
t1_stop = time.time()
print("(%s)执⾏结束,耗时%0.2f秒" % (os.getpid(), t1_stop - t1_start))
if __name__ == "__main__":
t_start = time.time()
print("当前程序进程(%s)" % os.getpid())
p1 = ProcessClass(2)
# 对⼀个不包含target属性的Process类执⾏start()⽅法,就会运⾏这个类中的run()⽅法,所以这⾥会执⾏p1.run()
p1.start()
p1.join()
t_stop = time.time()
print("(%s)执⾏结束,耗时%0.2f" % (os.getpid(), t_stop - t_start))
进程池
适用于创建成百上千个进程
初始化Pool时,可以指定⼀个最⼤进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建⼀个新的进程⽤来执⾏该请求;但如果池中的进程数已经达到指定的最⼤值,那么该请求就会等待。
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print("%s开始执⾏,进程号为%d" % (msg, os.getpid()))
# random.random()随机⽣成0~1之间的浮点数
time.sleep(random.random() * 2)
t_stop = time.time()
print(msg, "执⾏完毕,耗时%0.2f" % (t_stop - t_start))
if __name__ == "__main__":
po = Pool(3) # 定义⼀个进程池,最⼤进程数3
for i in range(0, 10):
# Pool.apply_async(要调⽤的⽬标,(传递给⽬标的参数元祖,))
# 每次循环将会⽤空闲出来的⼦进程去调⽤⽬标
po.apply_async(worker, (i,))
print("----start----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有⼦进程执⾏完成,必须放在close语句之后
print("-----end-----")
进程间通信:
queue的使用:
# coding=utf-8
from multiprocessing import Queue
q = Queue(3) # 初始化⼀个Queue对象,最多可接收三条put消息
q.put("消息1")
q.put("消息2")
print(q.full()) # False
q.put("消息3")
print(q.full()) # True
# 因为消息列队已满下⾯的try都会抛出异常,第⼀个try会等待2秒后再抛出异常,第⼆个Try会⽴刻抛出异常
try:
q.put("消息4", True, 2)
except:
print("消息列队已满,现有消息数量:%s" % q.qsize())
try:
q.put_nowait("消息4")
except:
print("消息列队已满,现有消息数量:%s" % q.qsize())
# 推荐的⽅式,先判断消息列队是否已满,再写⼊
if not q.full():
q.put_nowait("消息4")
# 读取消息时,先判断消息列队是否为空,再读取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
我们以Queue为例,在⽗进程中创建两个⼦进程,⼀个往Queue⾥写数据,⼀个从Queue⾥读数据:
from multiprocessing import Process, Queue
import time, random
# 写数据进程执⾏的代码:
def write(q):
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执⾏的代码:
def read(q):
while True:
if not q.empty():
value = q.get(True)
print("Get {0}".format(value))
time.sleep(random.random())
else:
break
if __name__ == "__main__":
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pw.join()
pr.start()
pr.join()
print("--end--")
子进程
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还需要控制子进程的输入和输出。
subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
下面的例子演示了如何在Python代码中运行命令nslookup www.python.org,这和命令行直接运行的效果是一样的:
import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)
运行结果:
$ nslookup www.python.org
Server: 192.168.19.4
Address: 192.168.19.4#53
Non-authoritative answer:
www.python.org canonical name = python.map.fastly.net.
Name: python.map.fastly.net
Address: 199.27.79.223
Exit code: 0
如果子进程还需要输入,则可以通过communicate()方法输入:
import subprocess
print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
上面的代码相当于在命令行执行命令nslookup,然后手动输入:
set q=mx
python.org
exit
运行结果如下:
$ nslookup
Server: 192.168.19.4
Address: 192.168.19.4#53
Non-authoritative answer:
python.org mail exchanger = 50 mail.python.org.
Authoritative answers can be found from:
mail.python.org internet address = 82.94.164.166
mail.python.org has AAAA address 2001:888:2000:d::a6
Exit code: 0
线程
线程的创建
实例1
# coding=utf-8
import threading
import time
def saySorry():
print("亲爱的,我错了,我能吃饭了吗?")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
t = threading.Thread(target=saySorry)
t.start() # 启动线程,即让线程开始
实例2
# coding=utf-8
import threading
from time import sleep, ctime
def sing():
for i in range(3):
print("正在唱歌...%d" % i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d" % i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s' % ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
# 屏蔽下面这行代码,试试看,程序是否会⽴⻢结束?
sleep(4)
print('---结束---:%s' % ctime())
线程数量、执行顺序、状态
查看线程数量
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---开始---:%s'%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print('当前运行的线程数为:%d'%length)
if length<=1:
break
sleep(0.5)
线程的执行顺序
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name属性中保存的是当前线程的名字
print(msg)
if __name__ == '__main__':
t = MyThread()
t.start()
注意:
1.每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
2.当线程的run()方法结束时该线程完成。
3.无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
多线程共享全局变量
from threading import Thread
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
print("----in work1, g_num is %d---"%g_num)
def work2():
global g_num
print("----in work2, g_num is %d---"%g_num)
print("---线程创建之前g_num is %d---"%g_num)
t1 = Thread(target=work1)
t1.start()
#延时一会,保证t1线程中的事情做完
time.sleep(1)
t2 = Thread(target=work2)
t2.start()
将列表当做实参
from threading import Thread
import time
def work1(nums):
nums.append(44)
print("----in work1---",nums)
def work2(nums):
#延时一会,保证t1线程中的事情做完
time.sleep(1)
print("----in work2---",nums)
g_nums = [11,22,33]
t1 = Thread(target=work1, args=(g_nums,))
t1.start()
t2 = Thread(target=work2, args=(g_nums,))
t2.start()
线程的同步
当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁。
互斥锁为资源引⼊⼀个状态:锁定/⾮锁定。
threading模块中定义了Lock类,可以⽅便的处理锁定:
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([blocking])
#释放
mutex.release()
其中,锁定⽅法acquire可以有⼀个blocking参数。
如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为⽌
(如果没有指定,那么默认为True)
如果设定blocking为False,则当前线程不会堵塞
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
mutex.acquire() # 上锁
g_num += 1
mutex.release() # 解锁
print("---test1---g_num=%d" % g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire() # 上锁
g_num += 1
mutex.release() # 解锁
print("---test2---g_num=%d" % g_num)
# 创建一个互斥锁
# 默认是未上锁的状态
mutex = threading.Lock()
# 创建2个线程,让他们各自对g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()
p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()
# 等待计算完成
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
对于非共享变量,不需要加锁
# coding=utf-8
import threading
import time
class MyThread(threading.Thread):
# 重写 构造方法
def __init__(self, num, sleepTime):
threading.Thread.__init__(self)
self.num = num
self.sleepTime = sleepTime
def run(self):
self.num += 1
time.sleep(self.sleepTime)
print('线程(%s),num=%d' % (self.name, self.num))
if __name__ == '__main__':
mutex = threading.Lock()
t1 = MyThread(100, 5)
t1.start()
t2 = MyThread(200, 1)
t2.start()