python(进程和多进程)

Linux系统如何创建子进程

  • Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,
    调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(
    称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
  • 子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork
    出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()
    就可以拿到父进程的ID。
  • Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松
    创建子进程;
原理:
  • 父进程和子进程:, 如果父进程结束, 子进程也随之结束;
  • 先有父进程, 再有子进程. 类Linux系统中(redhat,mac), fork函数;
常用函数:
  • os.fork()
  • os.getpid() # 获取当前进程的pid (process id)
  • os.getppid() # 获取当前进程的父进程pid (parent process id)
import os

print("当前进程(pid=%d)正在运行......." %(os.getpid()))
# 在pycharm编写代码, 程序的父进程就是pycharm;
print("当前进程的父进程为(pid=%d)正在运行....." %(os.getppid()))
print("开始创建子进程.....")

pid = os.fork()
if  pid == 0:
    print("这是子进程返回的是0, 子进程的pid为%d, 父进程为%d" %(os.getpid(), os.getppid()))
else:
    print("这是父进程返回的,返回值为子进程的pid, 为%d" %(pid))

在这里插入图片描述

multiprocess跨平台实现多线程

1.理解:
  • 如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?
  • 由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。
  • multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
  • join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
2. Process使用属性及方法

Process 类用来描述一个进程对象。创建子进程的时候,只需要传入一个执行函数和函数的参数即可完成 Process 示例的创建。

  • star() 方法启动进程,

  • join() 方法实现进程间的同步,等待所有进程退出。

  • close() 用来阻止多余的进程涌入进程池 Pool 造成进程阻塞。

  • multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
    target 是函数名字,需要调用的函数
    args 函数需要的参数,以 tuple 的形式传入

import multiprocessing
def job():
    print("当前子进程的名称%s....." %(multiprocessing.current_process()))

# 通过类的实例化实现
p1 = multiprocessing.Process(target=job, name="我的第一个子进程")
p1.start()

# 通过类的实例化实现
p2 = multiprocessing.Process(target=job, name="我的第2个子进程")
p2.start()


# join方法, 等待所有的子进程执行结束, 再执行主进程
p1.join()
p2.join()
print("任务执行结束.....")

在这里插入图片描述

类的继承实现多线程

import multiprocessing



class MyProcess(multiprocessing.Process):
    # 重写run方法=====start方法默认执行run方法
    def run(self):
        print("当前子进程的名称%s....." % (multiprocessing.current_process()))

p1 = MyProcess(name="first")
p1.start()
p2 = MyProcess(name="second")
p2.start()

p1.join()
p2.join()
print("all finish.....")

在这里插入图片描述

多进程案例效率演示

import threading
import time

from mytimeit import  timeit
import multiprocessing
def job(li):
    return  sum(li)
@timeit
def use_thread():
    li = range(1, 100000000)
    # create 5 threads
    threads = []
    for i in range(5):
        t = threading.Thread(target=job, args=(li, ))
        t.start()
        threads.append(t)
    [thread.join() for thread in  threads]

@timeit
def use_no_thread():
    li = range(1, 100000000)
    for i in range(5):
        job(li)


@timeit
def use_process():
    li = range(1, 100000000)
    # create 5 threads
    processes = []
    # 1). 开启的进程书是有瓶颈的, 取决于CPU个数,
    # 2). 如果处理的数据比较小, 不建议使用多进程,因为创建进程和销毁进程需要时间;
    # 3). 如果处理数据足够大, 0《进程数《cpu个数;
    for i in range(5):
        p = multiprocessing.Process(target=job, args=(li,))
        p.start()
        processes.append(p)
    [process.join() for process in processes]


if __name__ == "__main__":
    use_thread()
    use_process()
    use_no_thread()

在这里插入图片描述

进程锁

import multiprocessing
def work(f, item, lock):
    lock.acquire()
    try:
        with open(f, 'a+') as f:
            f.write("a %s task\n" % (item))
    except Exception as e:
        print("产生异常...")
    finally:
        lock.release()

def main():
    # 1). 实例化一个进程锁
    lock = multiprocessing.Lock()

    filename = 'doc/my.log'
    processes = []
    for i in range(4):
        p1 = multiprocessing.Process(target=work, args=(filename, i,lock))
        p1.start()
        processes.append(p1)

    [process.join() for process in  processes]
if __name__ == '__main__':
    main()

在这里插入图片描述

进程池的第一种实现方式

  • 在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量时间,如果操作的对象数目不大时,还可以直接适用Process类动态生成多个进程,几十个尚可,若上百个甚至更多时,手动限制进程数量就显得特别繁琐,此时进程池就显得尤为重要。
  • 进程池Pool类可以提供指定数量的进程供用户调用,当有新的请求提交至Pool中时,若进程池尚未满,就会创建一个新的进程来执行请求;若进程池中的进程数已经达到规定的最大数量,则该请求就会等待,直到进程池中有进程结束,才会创建新的进程来处理该请求。
import multiprocessing


def job(id):
    print("start %d...." % (id))
    print("end %d...." % (id))

# 创建进程池对象
pool = multiprocessing.Pool(processes=4)

# 给进程池分配任务;
for i in range(10):
    pool.apply_async(job, args=(i + 1,))
pool.close()
# 等待所有的子进程执行结束, 关闭进程池对象;
pool.join()
print("所有任务执行结束.....")

在这里插入图片描述

进程池的第2种实现方式

from concurrent.futures import  ProcessPoolExecutor


def job(id):
    print("start %d...." % (id))
    print("end %d...." % (id))

pool = ProcessPoolExecutor(max_workers=4)
#
# for id in range(10):
#     # 分配任务给子进程, 并且返回一个Future对象;
#     f1 = pool.submit(job, args=(id))
#     # 判断子进程是否执行结束?
#     print(f1.done())
#     # 查看该子进程执行的结果
#     print(f1.result())

pool.map(job, range(10))

在这里插入图片描述

多进程拷贝文本文件

拷贝的原理:
  • 读取源文件的内容;
  • 写入新的文件中;
import os
import time
import multiprocessing  # 进行进程间的通信, Queue
from queue import Queue


def copyFileTask(oldFolderName, newFolderName, filename, queue):
    """
    import os
    # 拼接生成绝对路径
    os.path.join('/mnt', 'file')
    '/mnt/file'
    os.path.join('/mnt/', 'file')
    '/mnt/file'


    :param oldFolderName: /root/day21/
    :param newFolderName: /root/day21_backup_201901
    :param filename: file1
    :return:
    """
    # 两者相同的效果, with语句执行节航速后, 自动关闭文件对象;
    # with open('/etc/passwd') as f:
    #     pass

    # f = open('/etc/passwd')
    # with f:
    #     pass

    fr = open(os.path.join(oldFolderName, filename), 'rb')
    fw = open(os.path.join(newFolderName, filename), 'wb')
    with fr, fw:
        content = fr.read(1024*3)
        while content:
            fw.write(content)
        queue.put(filename)
        # print(queue.qsize())

def main():
    # 判断备份目录是否存在
    while True:
        oldFolderName = input("请输入备份的目录名:")
        if os.path.exists(oldFolderName):
            break

    dateName = time.strftime('_%Y_%m_%d_%H_%M')  # '2019_01_20'
    newFolderName = oldFolderName + '_备份' + dateName
    if os.path.exists(newFolderName):
        os.rmdir(newFolderName)
    # 新建备份的目录;
    os.mkdir(newFolderName)
    print("正在创建备份目录%s....." % (newFolderName))
    # 获取备份目录中的所有文件名;
    fileNames = os.listdir(oldFolderName)

    # 队列, 存储已经备份的文件;
    # ****如果是用进程池,那么就需要使用Manager().Queue()队列才能在各子进程间通信,否则沒用
    queue = multiprocessing.Manager().Queue()
    # queue = Queue()

    pool = multiprocessing.Pool(4)

    for name in fileNames:

        # 给进程池分配任务
        pool.apply_async(copyFileTask, args=(oldFolderName,
                                             newFolderName,
                                             name,
                                             queue))

    # 100个文件, 1个文件   1%


    num = 0  # 当前备份的文件数
    allNum = len(fileNames)  # 总备份的文件数
    # print(num, allNum)
    while num < allNum:
        # print(queue.qsize())
        queue.get()
        num += 1
        copyRate = num / allNum  # 0.2322
        # \r使得光标不换行;
        print("\r\r备份的进度为%.2f%%" % (copyRate * 100), end='')
    pool.close()
    pool.join()
    print("备份成功;")

if __name__ == '__main__':
    main()

在这里插入图片描述

import os
import time
import multiprocessing  # 进行进程间的通信, Queue
from queue import Queue
from concurrent.futures import ProcessPoolExecutor


def copyFileTask(oldFolderName, newFolderName, filename, queue):
    """
    import os
    # 拼接生成绝对路径
    os.path.join('/mnt', 'file')
    '/mnt/file'
    os.path.join('/mnt/', 'file')
    '/mnt/file'


    :param oldFolderName: /root/day21/
    :param newFolderName: /root/day21_backup_201901
    :param filename: file1
    :return:
    """
    # 两者相同的效果, with语句执行节航速后, 自动关闭文件对象;
    # with open('/etc/passwd') as f:
    #     pass

    # f = open('/etc/passwd')
    # with f:
    #     pass

    fr = open(os.path.join(oldFolderName, filename), 'rb')
    fw = open(os.path.join(newFolderName, filename), 'wb')
    with fr, fw:
        content = fr.read(1024 * 3)
        while content:
            fw.write(content)
        queue.put(filename)
        # print(queue.qsize())


def main():
    # 判断备份目录是否存在
    while True:
        oldFolderName = input("请输入备份的目录名:")
        # oldFolderName = "/var/log/"
        if os.path.exists(oldFolderName):
            break
        else:
            print("%s目录不存在" % (oldFolderName))

    dateName = time.strftime('_%Y_%m_%d_%H_%M')  # '2019_01_20'
    newFolderName = oldFolderName + '_备份' + dateName
    if os.path.exists(newFolderName):
        # os.rmdir(newFolderName)  # 删除空目录
        # os.removedirs(newFolderName)
        os.system('rm -fr %s' %(newFolderName))
    # 新建备份的目录;
    os.mkdir(newFolderName)
    print("正在创建备份目录%s....." % (newFolderName))
    # 获取备份目录中的所有文件名;
    fileNames = os.listdir(oldFolderName)

    # 队列, 存储已经备份的文件;
    # ****如果是用进程池,那么就需要使用Manager().Queue()队列才能在各子进程间通信,否则沒用
    queue = multiprocessing.Manager().Queue()
    # queue = Queue()

    # pool = multiprocessing.Pool(4)
    pool = ProcessPoolExecutor(4)

    for name in fileNames:
        # 给进程池分配任务
        # pool.apply_async(copyFileTask, args=(oldFolderName,
        #                                      newFolderName,
        #                                      name,
        #                                      queue))

        pool.submit(copyFileTask, oldFolderName, newFolderName, name, queue)

    # 100个文件, 1个文件   1%

    num = 0  # 当前备份的文件数
    allNum = len(fileNames)  # 总备份的文件数
    # print(num, allNum)
    while num < allNum:
        # print(queue.qsize())
        queue.get()
        num += 1
        copyRate = num / allNum  # 0.2322
        # \r使得光标不换行;
        print("\r\r备份的进度为%.2f%%" % (copyRate * 100), end='')

    print("备份成功;")


if __name__ == '__main__':
    main()

在这里插入图片描述

进程间的通信之生产者消费者模型

import multiprocessing
# 线程通信=====(队列) ---- from queue import Queue
# 进程池中进程通信=====(队列) --- from multiprocess.Manager import Queue
# 多进程通信=========(队列)   ---- from multiprocess import Queue
import time
class Producer(multiprocessing.Process):
    def __init__(self, queue):
        super(Producer, self).__init__()
        self.queue = queue
    def run(self):
        # 将需要通信的数据写入队列中;
        for i in range(10):
            self.queue.put(i)
            time.sleep(0.1)
            print("传递消息, 内容为%s" %(i))
class Consumer(multiprocessing.Process):
    def __init__(self, queue):
        super(Consumer, self).__init__()
        self.queue = queue
    def run(self):
        while True:
            time.sleep(0.1)
            recvData = self.queue.get()
            print("接受到另一进程传递的数据: %s" %(recvData))


if __name__ == '__main__':
    q =  multiprocessing.Queue()
    p1 = Producer(q)
    c1 = Consumer(q)

    p1.start()
    c1.start()
    p1.join()
    c1.join()


在这里插入图片描述

进程间的通信之管道Pipe

  • Pipe管道,进程间通信的方式, l类似于 ls | wc -l;
  • Pipe()返回两个连接对象, 分别代表管道的两边;
  • 管道通信操作的方法: send(), recv;
  • 管道间的通信是双向的, 既可以发送,也可以接收;
import multiprocessing
# 线程通信=====(队列) ---- from queue import Queue
# 进程池中进程通信=====(队列) --- from multiprocess.Manager import Queue
# 多进程通信=========(队列)   ---- from multiprocess import Queue
import time
def after(conn):
    while True:
        print("接收到数据:", conn.recv())
        time.sleep(1)
def before(conn):
    while True:
        data = [42, None, 34, 'hello']
        conn.send(data)
        print("正在发送数据:%s" % (data))
        time.sleep(1)

def main():
    # send recv
    before_conn, after_conn = multiprocessing.Pipe()

    p1 = multiprocessing.Process(target=after, args=(after_conn,))
    p1.start()

    p2 = multiprocessing.Process(target=before, args=(before_conn,))
    p2.start()

    p1.join()
    p2.join()


if __name__ == '__main__':
    main()

在这里插入图片描述

分布式进程

  • 当处理大量数据的项目的时候,需要多台计算机共同完成这个项目时,需要用到分布式进程。
master
import random
from queue import  Queue
# BaseManager: 提供了不同机器之间共享数据的一种方法(ip:port)
from multiprocessing.managers import  BaseManager

# 1. 创建存储任务需要的队列
task_queue = Queue()

# 2. 存储任务执行结果的队列
result_queue = Queue()


# 3. 将队列注册到网上(使得其他主机也可以访问)
BaseManager.register('get_task_queue', callable=lambda : task_queue)
BaseManager.register('get_result_queue', callable=lambda : result_queue)


# 绑定ip和端口, 并且来个暗号;
manager = BaseManager(address=('172.25.254.250', 4000), authkey=b'westos')


# 4. 启动manager对象, 开始共享队列
manager.start()
# 5. 通过网络访问共享的Queue对象;
# BaseManager.register会注册一个方法, 当调用方法时, 执行函数lambda : task_queue;
task = manager.get_task_queue()
result = manager.get_result_queue()

# 6. 往队列里面放执行任务需要的数据;
for i in range(1000):
    # 模拟有1000个数字;
    n = random.randint(1, 100)
    task.put(n)
    print("任务列表中加入任务: %d" %(n))

# 7. 从result队列中读取各个机器中任务执行的结果;
for i in range(1000):
    res = result.get()
    print("队列任务执行的result: %s" %(res))


#  8. 关闭manager对象, 取消共享的队列
manager.shutdown()
slave1
import time
from multiprocessing.managers import BaseManager

# 1. 连接Master端, 获取共享的队列;ip是master端的ip, port'也是master端manager进程绑定的端口;
slave = BaseManager(address=('172.25.254.250', 4000), authkey=b'westos')

# 2. 注册队列, 获取共享的队列内容;
BaseManager.register('get_task_queue')
BaseManager.register('get_result_queue')

# 3. 连接master端;
slave.connect()

# 4. 通过网络访问共享的队列;
task = slave.get_task_queue()
result = slave.get_result_queue()

# 5. 读取管理端共享的任务, 并依次执行;
for i in range(500):
    n = task.get()
    print("slave1 运行任务 %d ** 2: " % (n))
    res = "slave1: %d ** 2 = %d" % (n, n ** 2)
    time.sleep(1)
    # 将任务的运行结果放入队列中;
    result.put(res)

print("执行结束........")
slave2
import time
from multiprocessing.managers import BaseManager

# 1. 连接Master端, 获取共享的队列;ip是master端的ip, port'也是master端manager进程绑定的端口;
slave = BaseManager(address=('172.25.254.250', 4000), authkey=b'westos')

# 2. 注册队列, 获取共享的队列内容;
BaseManager.register('get_task_queue')
BaseManager.register('get_result_queue')

# 3. 连接master端;
slave.connect()

# 4. 通过网络访问共享的队列;
task = slave.get_task_queue()
result = slave.get_result_queue()

# 5. 读取管理端共享的任务, 并依次执行;
for i in range(500):
    n = task.get()
    print("slave2: 运行任务 %d ** 2: " % (n))
    res = "slave2: %d ** 2 = %d" % (n, n ** 2)
    time.sleep(1)
    # 将任务的运行结果放入队列中;
    result.put(res)

print("执行结束........")

在这里插入图片描述

多进程多线程总结

1.什么是进程?什么是线程?

  • 进程是表示资源分配的基本单位,又是调度运行的基本单位。
    例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放入进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。
  • 线程是进程中执行运算的最小单位,如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。例如,假设用户启子任务;在产生工资单报表的过程中,用户又可以输人数据库查询请求,这又是一个子任务。
  • 多线程就像是火车上的每节车厢,而进程就是火车。

2.多进程和多线程的区别?

  • 数据共享、同步:
    1). 数据共享复杂,需要用IPC;数据是分开的,同步简单
    2). 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂
    各有优势
  • 内存、CPU
    1). 占用内存多,切换复杂,CPU利用率低
    2). 占用内存少,切换简单,CPU利用率高
    线程占优
  • 创建销毁、切换
    1). 创建销毁、切换复杂,速度慢
    2). 创建销毁、切换简单,速度很快
    线程占优
  • 编程、调试
    1). 编程简单,调试简单
    2). 编程复杂,调试复杂
    进程占优
  • 可靠性
    1). 进程间不会互相影响
    2). 一个线程挂掉将导致整个进程挂掉
    进程占优
  • 分布式
    1). 适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单
    2). 适应于多核分布式
    进程占优

3.进程之间的通信方式以及优缺点?

管道, 信号量, 信号, 消息队列, 共享内存, 套接字

4.线程之间的通信方式?

锁机制:包括互斥锁、条件变量、读写锁
信号量机制(Semaphore)
信号机制(Signal)

5.什么时候用多线程?什么时候用多进程?

  • 需要频繁创建销毁的优先用线程
  • 需要进行大量计算的优先使用进程
  • 可能要扩展到多机分布的用进程,多核分布的用线程

猜你喜欢

转载自blog.csdn.net/qq_43194257/article/details/87083383