Loading

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()

线程间的通信

常见的线程间的通信方式:

  1. 共享变量
  2. 消息队列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):关闭线程池。如果 waitTrue 则此方法只有在所有待执行的 Future 对象完成执行且释放已分配的资源后才会返回。 如果 waitFalse,则立即返回。

将任务提交给线程池执行后,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")
posted @ 2021-07-05 09:54  charlatte  阅读(52)  评论(0编辑  收藏  举报