进程和线程

75 阅读9分钟

程序与进程

程序

  • Program,一系列指令的集合
  • 使用编程语言所编写的指令的集合,用于实现一定的功能

进程

  • 启动后的程序在系统中就有一个进程,系统会为这个进程分配内存空间

创建进程的方式

os.fork()函数

  • 只适用于Unix、Linux、Mac操作系统

multiprocessing模块

  • 自定义类继承Process
  • 重写run方法
  • 语法结构:Process(group=None, target, name, args, kwargs)
from multiprocessing import Process
import os, time

# 定义函数,函数中的代码就是进程要执行的代码
def test():
    print(f"我是子进程,我的PID是:{os.getpid()},我的父进程是:{os.getppid()}")

if __name__ == '__main__':
    start = time.time()
    lst = []
    print('主进程开始执行')
    # 创建5个子进程
    for i in range(5):
        # 创建一个子进程对象
        pro = Process(target=test)
        # 启动进程
        pro.start()
        # 启动的进程添加到列表中
        lst.append(pro)
    # 列表中的每个成员都是一个进程
    for item in lst:
        # 阻塞主进程
        item.join()

    print(f'运行5个子进程一共花了:{time.time() - start}')
    print('主进程执行结束')    # 只是主进程钟没有代码了,并不是主进程结束,等待子进程执行结束主进程才结束

Pool进程池

  • 语法结构:p = Pool(N)

image.png

from multiprocessing import Pool
import time
import os

# 编写任务
def task(name):
    print(f'子进程的PID:{os.getpid()},执行任务 --- task --- {name}')
    time.sleep(1)

if __name__ == '__main__':
    start = time.time()
    print('父进程开始执行')
    # 创建一个进程池
    p = Pool(3)  # 进程池里,最大进程的个数3

    # 创建10个进程
    for i in range(10):
        # 以非阻塞方式进行
        # p.apply_async(func=task, args=(i,))
        # 以阻塞方式进行
        p.apply(task, args=(i,))
    p.close()   # 关闭进程池,不在接收新任务
    p.join()    # 阻塞父进程,等待子进程结束
    print('所有子进程结束,父进程结束')
    print(time.time() - start)

Process类的常用方法和属性

image.png

from multiprocessing import Process
import os
import time


def sub_precess1(name):
    print(f'子进程PID: {os.getpid()},父进程PID:{os.getppid()} -------- {name}')
    time.sleep(1)


def sub_precess2(age):
    print(f'子进程PID: {os.getpid()},父进程PID:{os.getppid()} -------- {age}')
    time.sleep(1)


if __name__ == '__main__':
    print('父进程开始执行')
    for i in range(5):
        # 创建子进程1
        p1 = Process(target=sub_precess1, args=('ivan',))
        # 创建子进程2
        p2 = Process(target=sub_precess2, args=('18',))
        # 启动子进程
        p1.start()
        p2.start()
        print(p1.name, '是否执行完毕', p1.is_alive())
        print(p2.name, '是否执行完毕', p2.is_alive())
        print(p1.name, '的PID是:', p1.pid)
        print(p2.name, '的PID是:', p2.pid)
        p1.join()   # 等待p1执行结束
        p2.join()   # 等待p2执行结束
    print('父进程执行结束')
from multiprocessing import Process
import os
import time


def sub_precess1(name):
    print(f'子进程PID: {os.getpid()},父进程PID:{os.getppid()} -------- {name}')
    time.sleep(1)


def sub_precess2(age):
    print(f'子进程PID: {os.getpid()},父进程PID:{os.getppid()} -------- {age}')
    time.sleep(1)


if __name__ == '__main__':
    print('父进程开始执行')
    for i in range(5):
        # 创建子进程1
        # p1 = Process()  # 没有给定target参数,默认调用run方法
        # 创建子进程2
        # p2 = Process()
        # 启动子进程
        # p1.start()  # 自动调用run方法执行
        # p2.start()
        # p1.run()
        # p2.run()

        p1 = Process(target=sub_precess1, args=('ivan',))
        p2 = Process(target=sub_precess2, args=(18,))
        p1.start()
        p2.start()
        # 终止进程
        p1.terminate()
        p2.terminate()
    print('父进程执行结束')

多个进程同时执行的顺序是随机的,不会根据创建的顺序执行

并发和并行

并发

  • 两个或多个事件同一时间间隔发生,多个进程被交替轮换着执行

image.png

并行

  • 两个或多个事件在同一时刻发生,多条命令在同一时刻在多个处理器上同时执行

image.png

进程之间的通信

多个进程间的数据可以共享吗?

image.png

  • 全局变量a在父进程和两个子进程中各一份,各自操作各自a的值,对于a的计算结果并没有在进程之间传递,进程之间并没有共享数据
from multiprocessing import Process

a = 100


def add():
    print('子进程1开始执行')
    global a
    a += 30
    print('a=', a)
    print('子进程1执行结束')


def sub():
    print('子进程2开始执行')
    global a
    a -= 50
    print('a=', a)
    print('子进程2执行结束')


if __name__ == '__main__':
    print('父进程开始执行')
    print('a的值为:', a)
    # 创建加的进程
    p1 = Process(target=add)
    # 创建减的进程
    p2 = Process(target=sub)
    # 启动两个子进程
    p1.start()
    p2.start()
    # 等待进程执行结束
    p1.join()
    p2.join()

    print('父进程执行结束')

进程之间的通信

队列(Queue)

  • 先进先出,First In First Out

image.png

multiprocessing模块的Queue类

语法结构q = Queue(N)

image.png

from multiprocessing import Queue

if __name__ == '__main__':
    # 创建一个队列,最多可以接收3条消息
    q = Queue(3)
    print('队列是否为空:', q.empty())
    print('队列是否为满:', q.full())
    # 向队列中添加消息
    q.put('hello')    # block默认为True
    q.put('world')
    print('队列是否为空:', q.empty())
    print('队列是否为满:', q.full())
    q.put('python')
    print('队列是否为空:', q.empty())
    print('队列是否为满:', q.full())
    print('队列当中消息的个数:', q.qsize())
    # 队列中已经满了,还能否添加数据呢?
    # q.put('html')   # 队列已满,block=True默认为True,一直等待队列有空位置才会将html入队,然后继续执行
    # q.put('html', block=True, timeout=2)    # 等待两秒,还灭有空位置,抛出异常queue.Full
    # q.put_nowait('html')    # 相当于q.put('html', block=False)     # 抛出异常queue.Full
    print('---------------')
    # 从列表中获取消息,出队操作
    print(q.get())
    print('出队之后,消息的个数:', q.qsize())
    q.put_nowait('html')    # 入队
    print('入队之后,消息的个数:', q.qsize())
    # 通过遍历出队所有元素
    if not q.empty():   # 判断队列是否为空
        for i in range(q.qsize()):
            print(q.get_nowait())

    print('队列是否为空:', q.empty())
    print('队列是否为满:', q.full())
    print('队列当中消息的个数:', q.qsize())

使用队列实现进程之间的通信

  • 一个进程负责向队列中写入数据
  • 一个进程负责从队列中读取数据

image.png

from multiprocessing import Process, Queue
import time

a = 100
# 向队列中写入消息的进程要执行的函数
def write_msg(q):
    global a
    if not q.full():
        for i in range(1, 6):
            a -= 10
            q.put(a)    # 入队操作,将a的值进行入队
            print('a入队时的值:', a)

# 从队列中读取消息的进程要执行的函数
def read_msg(q):
    time.sleep(1)
    while not q.empty():
        print('出队a的值:', q.get())

if __name__ == '__main__':
    print('父进程开始执行')
    q = Queue() # 有父进程创建队列,传给子进程,没有写个数,说明队列可接收的消息个数没有上限
    # 创建两个子进程
    p1 = Process(target=write_msg, args=(q,))
    p2 = Process(target=read_msg, args=(q,))
    # 启动两个子进程
    p1.start()
    p2.start()
    # 等待写入进程结束
    p1.join()
    # 等待读取进程结束
    p2.join()

    print('-----父进程执行结束-----')

多任务的方式

  • 一个应用程序内使用多个进程
  • 一个进程内使用多个线程

线程

  • CPU 可调度的最小单位
  • 被包含在进程中,是进程中实际的运作单位
  • 一个进程中可以并发多个线程,每条线程并行执行不同的任务

创建线程的方式

  • 使用threading模块创建线程
  • 使用函数式创建线程:t = Thread(group, target, name, args, kwargs)
# 多线程
import threading
from threading import Thread
import time

# 编写函数
def test():
    for i in range(3):
        time.sleep(1)
        print(f'线程{threading.current_thread().name}正在执行', {i})

if __name__ == '__main__':
    start = time.time()
    print('主线程开始执行')
    # for i in range(2):
    #     t = Thread(target=test)
    #     # 启动线程
    #     t.start()
    #     t.join()
    lst = [Thread(target=test) for i in range(2)]   # 使用列表生成式
    for item in lst:
        item.start()
    for item in lst:
        item.join()
    print('一共耗时:', time.time() - start)

使用Tread子类创建线程

  • 自定义类继承Thread类
  • 实现run方法
# 继承方式创建线程
import threading
from threading import Thread
import time


class SubThread(Thread):
    def run(self):
        for i in range(3):
            time.sleep(1)
            print(f'Thread {threading.current_thread().name} started, i = {i}')


if __name__ == '__main__':
    print('主线程开始执行')
    lst = [SubThread() for i in range(2)]  # 创建两个线程,放到列表当中存储
    for item in lst:
        item.start()
    for item in lst:
        item.join()
    print('主线程执行结束')

线程之间的数据共享

线程之间的通信

  • 一个进程内的所有线程共享全局变量

image.png

from threading import Thread

a = 100


def add():
    print('加的线程开始执行')
    global a
    a += 30
    print(f'a的值为:{a}')
    print('加的线程执行结束')


def sub():
    print('减的线程开始执行')
    global a
    a -= 50
    print(f'a的值为:{a}')
    print('减的线程执行结束')


if __name__ == '__main__':
    print('主线程开始执行')
    print(f'--------全局变量a的值{a}--------')
    add = Thread(target=add)
    sub = Thread(target=sub)
    # 启动线程
    add.start()
    sub.start()
    add.join()  # 等待加的线程结束
    sub.join()  # 等待减的线程结束
    print('--------主线程执行结束--------')

线程操作共享数据的安全性问题

多线程共享数据所带来的安全性问题

  • 多线程都是在同一个进程中运行的,因此在进程中的全局变量所有线程都可以共享,这就造成了一个问题,因为线程的顺序是无序的,有可能会造成数据错乱
import threading
from threading import Thread
import time

ticket = 50


def sale_ticket():
    global ticket
    for i in range(1000):
        if ticket > 0:
            print(threading.current_thread().name + f'正在出售第{ticket}张票')
            ticket -= 1
        time.sleep(1)


if __name__ == '__main__':
    lst = [Thread(target=sale_ticket) for i in range(3)]
    for item in lst:
        item.start()

锁机制

  • 互斥锁的两种状态:锁定和非锁定
  • 当线程访问共享资源时,先将其“锁定”,其他线程不能修改;直到该线程释放资源,将资源的状态变成“非锁定”时,其他线程才能操作共享资源

锁机制

threading中的Lock类

创建锁对象:lock_obj = Lock()

Lock类的方法

  • 上锁(锁定):obj.acquire()
  • 释放锁:obj.release()
import threading
from threading import Thread, Lock
import time

ticket = 50
lock_obj = Lock()  # 创建锁对象


def sale_ticket():
    global ticket
    for i in range(1000):
        lock_obj.acquire()  # 加锁
        if ticket > 0:
            print(threading.current_thread().name + f'正在出售第{ticket}张票')
            ticket -= 1
        lock_obj.release()  # 释放锁
        time.sleep(1)


if __name__ == '__main__':
    lst = [Thread(target=sale_ticket) for i in range(3)]
    for item in lst:
        item.start()

使用锁的原则

  • 把尽量少的和不耗时的代码放到锁中执行
  • 代码执行完成后要记得释放锁

生产者模式和消费者模式

多个线程之间通信可以采用生产者模式和消费者模式

生产者线程

  • 生产者线程用于“生产”数据

消费者线程

  • 消费者线程用于“处理”数据

image.png

线程中的队列

Queue可以实现进程之间的通信,也可以实现线程之间的通信

使用Queue实现生产者与消费者问题

  • 生产者将“数据”放入队列
  • 消费者从队列中“取出”数据
from queue import Queue # 实现线程之间的通信
# from multiprocessing import Queue
from threading import Thread
import time

# 创建一个生产者类
class Producer(Thread):
    def __init__(self, name, queue):
        Thread.__init__(self, name=name)
        self.queue = queue

    def run(self):
        for i in range(1, 6):
            print(f'{self.name} 将产品 {i} 放入队列')
            self.queue.put(i)
            time.sleep(1)
        print('生产者完成了所有数据的存放')

# 创建一个消费者类
class Consumer(Thread):
    def __init__(self, name, queue):
        Thread.__init__(self, name=name)
        self.queue = queue

    def run(self):
        for _ in range(5):
            value = self.queue.get()
            print(f'消费者线程 {self.name} 取出了 {value}')
            time.sleep(1)
        print('-------- 消费者线程完成了所有数据的取出 --------')

if __name__ == '__main__':
    # 创建队列
    queue = Queue()
    # 创建生产者线程
    producer = Producer('producer', queue)
    # 创建消费者线程
    consumer = Consumer('consumer', queue)
    # 启动线程
    producer.start()
    consumer.start()
    # 等待生产者线程结束,等待消费者线程结束
    producer.join()
    consumer.join()
    print('主线程运行结束')