Python进程和线程
进程
一个正在允许的程序或者软件就是一个进程,进程是操作系统进行资源分配的基本单位。一个程序运行后至少有一个进程,一个进程默认有一个线程。
在Windows操作系统中由于没有fork(Linux操作系统中创建进程的机制),必须把创建子进程的部分使用if __name__ == '__main__'
判断。
Python中的multiprocessing模块提供了Process类来实现进程相关的功能。
Process类参数说明:
- group: 指定进程组,一般使用None
- target: 子进程要执行的目标任务
- name: 进程名字
- args: 以元组方式给任务传参
- kwargs: 以字典方式给任务传参
Process类常用方法说明:
- start(): 启动子进程实例(创建子进程)
- join(): 等待子进程执行结束
- terminate(): 终止子进程
- is_alive():判断进程是否存活
使用进程的两种方式
使用进程的第一种方式:
# 1.导入进程包
import multiprocessing
import time
def task1():
for i in range(3):
print('task1 执行...')
time.sleep(0.2)
def task2():
for i in range(3):
print('task2 执行...')
time.sleep(0.2)
if __name__ == '__main__':
# 2.创建子进程,手动创建的进程称为子进程
# group: 进程组
# target: 目标任务
# name: 进程名,如果不指定,则默认是Process-1,...
task1_process = multiprocessing.Process(target=task1)
# 3.启动进程执行对应的任务
task1_process.start()
task2_process = multiprocessing.Process(target=task2)
task2_process.start()
使用进程的第二种方式:
继承Process类
import time
from multiprocessing import Process
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name} is run')
time.sleep(0.1)
print(f'{self.name} is done')
if __name__ == '__main__':
p = MyProcess("子进程-1")
p.start()
print("主进程")
获取进程的名称和编号
通过获取进程的编号可以得知子进程是由哪个主进程创建出来的。
获取当前执行的进程名称和编号
os.getpid()
示例:
# 1.导入进程包
import multiprocessing
import time
import os
def task1():
# 获取当前进程(子进程)的编号
task1_process_id = os.getpid()
# task1_process_id: 19920 <Process(task1_process, started)>
print('task1_process_id:', task1_process_id, multiprocessing.current_process())
for i in range(3):
print('task1 执行...')
time.sleep(0.2)
def task2():
# 获取当前进程(子进程)的编号
task2_process_id = os.getpid()
# task2_process_id: 17496 < Process(task2_process, started) >
print('task2_process_id:', task2_process_id, multiprocessing.current_process())
for i in range(3):
print('task2 执行...')
time.sleep(0.2)
if __name__ == '__main__':
# 2.创建子进程,手动创建的进程称为子进程
# group: 进程组
# target: 目标任务
# name: 进程名,如果不指定,则默认是Process-1,...
task1_process = multiprocessing.Process(target=task1, name='task1_process')
task2_process = multiprocessing.Process(target=task2, name='task2_process')
# 当没有指定name参数时,获取的进程名如下
# print('task1_process:', task1_process) # task1_process: <Process(Process-1, initial)>
# print('task2_process:', task2_process) # task2_process: <Process(Process-2, initial)>
# 当指定name参数时,获取的进程名为name名称
print('task1_process:', task1_process) # task1_process: <Process(task1_process, initial)>
print('task2_process:', task2_process) # task2_process: <Process(task2_process, initial)>
# 3.启动进程执行对应的任务
task1_process.start()
task2_process.start()
# 获取当前进程(主进程)的编号,multiprocessing.current_process()可以查看是由哪个进程执行的
main_process_id = os.getpid()
# main_process_id: 3608 <_MainProcess(MainProcess, started)>
print('main_process_id:', main_process_id, multiprocessing.current_process())
获取当前执行进程的父进程名称和编号
os.getppid()
示例:
# 导入进程包
import multiprocessing
import time
import os
def task1():
# 获取当前进程的父进程编号
task1_process_parent_id = os.getppid()
print('task1的父进程编号为:', task1_process_parent_id) # task1的父进程编号为: 14860
for i in range(3):
print('task1 执行...')
time.sleep(0.2)
def task2():
# 获取当前进程的父进程编号
task2_process_parent_id = os.getppid()
print('task2的父进程编号为:', task2_process_parent_id) # task2的父进程编号为: 14860
for i in range(3):
print('task2 执行...')
time.sleep(0.2)
if __name__ == '__main__':
task1_process = multiprocessing.Process(target=task1, name='task1_process')
task2_process = multiprocessing.Process(target=task2, name='task2_process')
# 启动进程执行对应的任务
task1_process.start()
task2_process.start()
# 获取当前进程(主进程)的编号,multiprocessing.current_process()可以查看是由哪个进程执行的
main_process_id = os.getpid()
# main_process_id: 14860 <_MainProcess(MainProcess, started)>
print('main_process_id:', main_process_id, multiprocessing.current_process())
根据进程编号杀死指定进程
根据进程pid杀死指定的进程
os.kill(pid, sig)
示例:
# 导入进程包
import multiprocessing
import time
import os
def task1():
for i in range(3):
print('task1 执行...')
time.sleep(0.2)
def task2():
# 获取当前进程(子进程)的编号
task2_process_id = os.getpid()
print('task2_process_id:', task2_process_id, multiprocessing.current_process())
for i in range(3):
print('task2 执行...')
time.sleep(0.2)
# 根据进程编号强制杀死指定进程
os.kill(task2_process_id, 9)
if __name__ == '__main__':
task1_process = multiprocessing.Process(target=task1, name='task1_process')
task2_process = multiprocessing.Process(target=task2, name='task2_process')
task1_process.start()
task2_process.start()
进程执行带有参数的任务
Process类执行任务并给任务传参数有两种方式:
- args: 以元组的方式给执行任务传参
- kwargs: 以字典的方式给执行任务传参
示例:
import multiprocessing
def show_info(name, age):
print(name, age)
if __name__ == '__main__':
# 以元组方式按照顺序传参
sub_process = multiprocessing.Process(target=show_info, args=("tom", 18))
sub_process.start()
# 以字典键值方式传参
sub_process = multiprocessing.Process(target=show_info, kwargs={"age": 20, "name": "jerry"})
sub_process.start()
# 元组和字典想结合的方式传参
sub_process = multiprocessing.Process(target=show_info, args=("张三",), kwargs={"age": "jerry"})
sub_process.start()
进程之间不共享全局变量
import multiprocessing
import time
g_list = []
# 添加数据的任务
def add_data():
for i in range(3):
g_list.append(i)
print("add:", i)
time.sleep(0.2)
# 读取数据的任务
def read_data():
print("read:", g_list)
add_process = multiprocessing.Process(target=add_data)
read_process = multiprocessing.Process(target=read_data)
if __name__ == '__main__':
add_process.start()
# 主进程等待添加数据的进程执行完毕后在向下执行
add_process.join()
read_process.start()
join(timeout)
:在当前位置阻塞主进程,等待执行join()的进程结束后再继续执行主进程的代码逻辑,timeout表示超时时间。
设置守护进程
可以将子进程设置守护进程,当主进程执行完毕后,子进程也随之终止。
# 默认值为False,设置为True代表为守护进程
sub_process.daemon = True
示例:
import multiprocessing
import time
def task():
while True:
print("任务执行中。。。")
time.sleep(0.2)
if __name__ == '__main__':
sub_process = multiprocessing.Process(target=task)
# 设置子进程为守护进程
sub_process.daemon = True
sub_process.start()
time.sleep(0.5)
print('over。。。')
杀死进程
terminate()
:关闭进程,不会立即关闭,有一定等待操作系统去关闭的时间。
示例:
import time
import multiprocessing
def task1(name):
print(f"{name} is run")
time.sleep(2)
print(f"{name} is done")
if __name__ == "__main__":
p = multiprocessing.Process(target=task1, args=("task1",))
p.start()
time.sleep(1)
p.terminate() # 杀死子进程
print("主进程")
判断进程是否存活
is_alive()
:使用terminate
关闭后,可以查看是否存活,有一定的等待操作系统关闭的时间。返回true表示未关闭,false表示已经关闭
import time
import multiprocessing
def task1(name):
print(f"{name} is run")
time.sleep(2)
print(f"{name} is done")
if __name__ == "__main__":
p = multiprocessing.Process(target=task1, args=("task1",))
p.start()
# 杀死子进程
p.terminate()
time.sleep(0.5)
# 判断进程是否存活
print(p.is_alive())
print("主进程")
进程间的通信
Queue(队列)
multiprocessing 模块的 Queue 可以实现多进程之间的数据传递。
import time
from multiprocessing import Process, Queue
def producer(queue):
queue.put("a")
time.sleep(2)
def consumer(queue):
time.sleep(2)
data = queue.get()
print("get data:{}".format(data))
if __name__ == '__main__':
queue = Queue(10)
my_producer = Process(target=producer, args=(queue,))
my_consumer = Process(target=consumer, args=(queue,))
my_producer.start()
my_consumer.start()
my_producer.join()
my_consumer.join()
Pipe(管道)
通过Pipe(管道)也可以实现进程间的通信,Pipe的性能要高于Queue,当只有两个进程时,优先使用Pipe。
import time
from multiprocessing import Process, Pipe
def producer(pipe):
# 发送数据
pipe.send("aaaaa")
time.sleep(2)
def consumer(pipe):
# 接收数据
print(pipe.recv())
if __name__ == '__main__':
# pipe只能适用于两个进程
receive_pipe, send_pipe = Pipe()
my_producer = Process(target=producer, args=(send_pipe,))
my_consumer = Process(target=consumer, args=(receive_pipe,))
my_producer.start()
my_consumer.start()
my_producer.join()
my_consumer.join()
线程
线程是cpu调度的基本单位,每一个进程至少一个线程。程序启动默认会有一个主线程,我们可以手动创建多个线程。
在Python3中,threading模块提供线程的功能。theading模块下的Thread类,是模块中最主要的线程类。
使用线程的两种方式
使用线程的第一种方式:
import threading
import time
def sing():
current_thread = threading.current_thread()
print("sing:", current_thread)
for i in range(3):
print("唱歌中...")
time.sleep(0.2)
def dance():
current_thread = threading.current_thread()
print("dance:", current_thread)
for i in range(3):
print("跳舞中...")
time.sleep(0.2)
if __name__ == '__main__':
# 获取当前线程
current_thread = threading.current_thread()
print("main:", current_thread)
# 创建子线程
sing_thread = threading.Thread(target=sing, name="sing_thread")
# 启动子线程
sing_thread.start()
# 创建子线程
dance_thread = threading.Thread(target=dance, name="dance_thread")
# 启动子线程
dance_thread.start()
使用线程的第二种方式:
import time
from threading import Thread
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name} is run')
time.sleep(0.1)
print(f'{self.name} is done')
if __name__ == '__main__':
t1 = MyThread("t1")
t1.start()
print("主线程")
线程执行带有参数的任务
import threading
def show_info(name, age):
print("name: %s age: %d" % (name, age))
if __name__ == '__main__':
# sub_thread = threading.Thread(target=show_info, args=("tom", 20))
# sub_thread.start()
sub_thread = threading.Thread(target=show_info, kwargs={"age": 18, "name": "jerry"})
sub_thread.start()
设置守护线程
import threading
import time
def task():
while True:
print("任务执行中...")
time.sleep(0.2)
if __name__ == '__main__':
# daemon=True,表示创建守护线程,主线程退出,子线程就销毁了
# 方式1
# sub_thread = threading.Thread(target=task, daemon=True)
# 方式2
sub_thread = threading.Thread(target=task)
sub_thread.setDaemon(True)
sub_thread.start()
# 主线程延迟1秒
time.sleep(1)
print("over")
线程之间共享全局变量
import threading
import time
# 定义全局变量
g_list = []
# 添加数据的任务
def add_data():
for i in range(3):
g_list.append(i)
print("add:", i)
time.sleep(0.2)
print("添加数据任务完成:", g_list)
# 读取数据的任务
def read_data():
print(g_list)
if __name__ == '__main__':
add_thread = threading.Thread(target=add_data)
read_thread = threading.Thread(target=read_data)
add_thread.start()
# 让主线程等待添加数据的子线程执行完成之后再向下执行
add_thread.join()
read_thread.start()
join(timeout)
:让主线程阻塞,直到被调用线程运行结束或超时,timeout表示超时时间。
互斥锁
互斥锁可以保证同一时刻只有一个线程可以访问共享的数据。互斥锁是多个线程一起去抢,抢到锁的线程会先执行,没有抢到锁的线程需要等待。
import threading
# 全局变量
g_num = 0
# 创建互斥锁
lock = threading.Lock()
def task1():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num = g_num + 1
print("task1: ", g_num)
# 释放锁
lock.release()
def task2():
# 上锁
lock.acquire()
for i in range(1000000):
global g_num
g_num = g_num + 1
print("task2: ", g_num)
# 释放锁
lock.release()
if __name__ == '__main__':
task1_thread = threading.Thread(target=task1)
task2_thread = threading.Thread(target=task2)
task1_thread.start()
task2_thread.start()
线程间的通信
常见的线程间的通信方式:
- 共享变量
- 消息队列Queue
共享变量
import threading
nums = 0
def addNum():
global nums
for i in range(10000000):
nums += 1
def reduceNum():
global nums
for j in range(10000000):
nums -= 1
t1 = threading.Thread(target=addNum)
t2 = threading.Thread(target=reduceNum)
t1.start()
t2.start()
t1.join()
t2.join()
采用共享变量存在线程不安全的问题,需要加锁解决。
Queue
使用Queue(队列)是比较常用的一种实现线程间通信的方式。
创建一个先进先出的队列:
queue = Queue(maxsize=0):
maxsize
:用来指定最大值,如果指定最大值,当队列没有空间时会发生阻塞。
创建一个后进先出的队列:
lifoQueue = LifoQueue(maxsize=0)
创建一个优先级队列:
priorityQueue = PriorityQueue(maxsize=0)
常用的函数:
函数名 | 描述 |
---|---|
qsize() | 返回队列的大小 |
empty() | 如果队列为空,返回 True,否则返回 False |
full() | 如果队列已满,返回 True,否则返回 False |
put(item, block=True, timeout=None) | 将 item 放入队列,如果 block 为 True(默认)且 timeout 为 None,则在有可用空间之前阻塞。如果 timeout 为正值,则最多阻塞 timeout 秒。如果block为 False,则抛出Empty异常 |
get(block=True, timeout=None) | 从队列中取出元素,如果给定了block,则一直阻塞到有可用的元素为止 |
task_done() | 用于表示队列任务处理已经完成,当所有任务都处理完成时,join() 阻塞将会解除 |
join() | 在队列中所有元素执行完毕并调用task_done()之前,保持阻塞 |
示例:
from queue import Queue, LifoQueue, PriorityQueue
import threading
def producer(queue):
for i in range(50):
print("put {}".format(i))
queue.put(i)
print("size now {}".format(queue.qsize()))
def consumer(queue):
while True:
if queue.empty():
print("队列已经为空")
break
else:
print("get {}".format(queue.get()))
if __name__ == '__main__':
queue = Queue(50)
t1 = threading.Thread(target=producer, args=(queue,))
t2 = threading.Thread(target=consumer, args=(queue,))
t1.start()
t2.start()
t1.join()
t2.join()
线程同步
除了用Lock锁的方式实现线程同步外,还可以使用RLock(可重入锁)、Condition和Semaphore的方式。
RLock
有的时候需要多次acquire(),使用Lock锁的话就会引起死锁,这时候就需要用到 RLock (可重入锁),在同一个线程里面,可以连续调用多次acquire, 一定要注意acquire的次数要和release的次数相等。
from threading import RLock
Condition
Condition除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。
Condition常用的函数:
函数名 | 描述 |
---|---|
acquire() | 获得锁 |
release() | 释放锁 |
wait(timeout=None) | 等待直到被通知或发生超时timeout秒才会被唤醒继续运行。 |
notify(n=1) | 默认唤醒一个正在等待该condition的线程,可最多唤醒n个正在等待的线程。 |
notify_all() | 唤醒所有正在等待这个Condition的线程 |
注:Condition实现了 __enter__
和 __exit__
方法,所以acquire与release方法可以用with语句代替。
示例:两个线程实现对话
import threading
from threading import Condition
class XiaoMing(threading.Thread):
def __init__(self, cond):
super().__init__(name="小明")
self.cond = cond
def run(self):
with self.cond:
self.cond.wait()
print("{}:在呀".format(self.name))
self.cond.notify()
self.cond.wait()
print("{}:我在学习呀".format(self.name))
self.cond.notify()
self.cond.wait()
print("{}:现在就去吧".format(self.name))
self.cond.notify()
class XiaoWang(threading.Thread):
def __init__(self, cond):
super().__init__(name="小王")
self.cond = cond
def run(self):
with self.cond:
print("{}:小明同学在吗?".format(self.name))
self.cond.notify()
self.cond.wait()
print("{}:你在干嘛呀?".format(self.name))
self.cond.notify()
self.cond.wait()
print("{}:一会去打游戏吗?".format(self.name))
self.cond.notify()
self.cond.wait()
if __name__ == '__main__':
cond = Condition()
xiaoming = XiaoMing(cond)
xiaowang = XiaoWang(cond)
# 启动顺序有要求
xiaoming.start()
xiaowang.start()
Semaphore
在Semaphore(信号量)中有一个内置的计数器,能够标明当前的共享资源可以有多少线程同时读取。
定义一个最多能同时运行5个线程的信号量:
semaphore = threading.Semaphore(5)
当调用 acquire()
方法时,内置的计数器就会减一 :
semaphore.acquire()
当调用 release()
方法时,内置的计数器就会加一:
semaphore.release()
当计数器为零时,acquire()调用被阻塞,直到release()释放信号量为止。
示例:
import threading
import time
class HtmlSpider(threading.Thread):
def __init__(self, url, sem):
super().__init__()
self.url = url
self.sem = sem
def run(self):
time.sleep(2)
print('Got html successful!')
sem.release()
class UrlProducer(threading.Thread):
def __init__(self, sem):
super().__init__()
self.sem = sem
def run(self):
for i in range(20):
sem.acquire()
thread_html = HtmlSpider("https://www.baidu.com/{}".format(i), self.sem)
thread_html.start()
if __name__ == "__main__":
sem = threading.Semaphore(3)
producer_thread = UrlProducer(sem)
producer_thread.start()
线程池
系统在启动一个新线程时由于涉及与操作系统的交互,所以是有一定的成本的。在这种情况下,使用线程池可以很好地提升性能。
从Python3.2开始,标准库为我们提供了concurrent.futures
模块,该模块的 Executor
类提供了两个子类ThreadPoolExecutor
(线程池)和ProcessPoolExecutor
(进程池)两个类来创建线程池和进程池。
文档:https://docs.python.org/zh-cn/3/library/concurrent.futures.html
Executor 提供的常用方法:
submit(fn, *args, **kwargs)
:将fn
函数提交给线程池。*args
和*kwargs
用来给为fn
函数传入参数。map(func, *iterables, timeout=None, chunksize=1)
:该函数类似于高阶函数函数map(func, *iterables)
,只是该函数将会启动多个线程,以异步方式立即对iterables
执行map
处理。chunksize
参数对 ThreadPoolExecutor 没有效果。shutdown(wait=True)
:关闭线程池。如果wait
为True
则此方法只有在所有待执行的 Future 对象完成执行且释放已分配的资源后才会返回。 如果wait
为False
,则立即返回。
将任务提交给线程池执行后,submit
方法会返回一个 Future
对象,Future
类主要用于获取线程任务函数的返回值。
from concurrent.futures import Future
Future 提供的主要方法:
cancel()
:取消线程任务。如果该任务正在执行,则不可取消,则该方法返回 False。否则,程序会取消该任务,并返回 True。cancelled()
:返回线程任务是否被成功取消。result(timeout=None)
:获取线程任务最后返回的结果。如果线程任务还未完成,该方法将会阻塞当前线程,其中timeout
参数可以指定最多阻塞多少秒。done()
:如果线程任务被成功取消或执行完成,则该方法返回 True。
示例:
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 使用线程池的好处:主线程可以获取某个线程的状态以及返回值
def task(times):
time.sleep(times)
print("get {} success\n".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
# 通过submit函数提交要执行的任务到线程池中
t1 = executor.submit(task, (3))
t2 = executor.submit(task, (2))
# done方法可以用来判断任务是否执行完成
print(t1.done())
print(t2.cancel())
time.sleep(3)
print(t1.done())
print(t2.done())
# result方法可以获取任务的执行结果即返回值
print(t1.result())
注:
- 如果没有指定max_workers,则默认为:
max_workers = min(32, (os.cpu_count() or 1) + 4)
- 可以使用with语句开启线程池:
# 创建一个最大容纳数量为4的线程池
with ThreadPoolExecutor(max_workers=4) as executor:
...
as_completed
判断任务是否结束最好的方法是当某个任务结束后,就给主线程返回结果,而不是一直判断每个任务是否结束。而 as_completed()
方法就可以达到当某个任务结束后就返回结果的效果。as_completed方法是一个生成器,在没有任务完成的时候,会阻塞,在有某个任务完成的时候,会yield这个任务,就能执行for循环下面的语句,然后继续阻塞住,循环到所有的任务结束。
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 使用线程池的好处:主线程可以获取某个线程的状态以及返回值
def task(times):
time.sleep(times)
print("get {} success\n".format(times))
return times
# max_workers参数可以设置线程池中最多能同时运行的线程数目
executor = ThreadPoolExecutor(max_workers=2)
# 获取已经成功的task的返回
task_list = [3, 2, 4]
all_task = [executor.submit(task, t) for t in task_list]
for f in as_completed(all_task):
data = f.result()
print("get {}".format(data))
map
通过executor的map方法也可以获取已经完成的task。
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
# 使用线程池的好处:主线程可以获取某个线程的状态以及返回值
def task(times):
time.sleep(times)
print("get {} success\n".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
# 获取已经成功的task的返回
task_list = [3, 2, 4]
for data in executor.map(task, task_list):
print(data)
map
方法与python
中高阶函数map
类似,都是将序列中的每个元素都应用到同一个函数。
wait
wait
方法可以让主线程阻塞。
可选的参数:
- fs:等待的任务序列
- timeout:超时时间
- return_when:该参数可以设置等待条件。return_when默认为ALL_COMPLETED
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
import time
# 使用线程池的好处:主线程可以获取某个线程的状态以及返回值
def task(times):
time.sleep(times)
print("get {} success\n".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
task_list = [3, 2, 4]
all_task = [executor.submit(task, t) for t in task_list]
wait(all_task, return_when=FIRST_COMPLETED)
print("main")