【多进程&多线程】基础方法和使用心得记录_基于python3

注意:DataFrame.values 返回的是numpy.array(a) ; 如果要相加 train_df_list += a; 需要转化 a.tolist() !!!

零、引言

0.1 前言

在使用多进程和多线程过程中犯了很多错误;也学到了很多基础技法。

为了方便调试和未来不再犯类似错误;方便进行快速的操作、使用 特记录如下。

0.2 二者选择

  1. 前言:因为GIL的限制,python的线程是无法真正意义上并行的。相对于异步编程,其性能可以说不是一个等量级的。为什么我们还要学习多线程编程呢,虽然说异步编程好处多,但编程也较为复杂,逻辑不容易理解,学习成本和维护成本都比较高。毕竟我们大部分人还是适应同步编码的,除非一些需要高性能处理的地方采用异步。

  2. 且由于GIL(全局解释器)的存在,该机制限制每个python进程中有且只有一个线程同时运行,也就是说即使写了threading,也于事无补,cpu只是在多个thread之间来回跳而已,并没有并发执行这些thread。所以以后要写并发机制,还得用多进程multiprocessing模块

0.3 首先普及下进程和线程的概念:

进程:进程是操作系统资源分配的基本单位。
线程:线程是任务调度和执行的基本单位。
一个应用程序至少一个进程,一个进程至少一个线程。
两者区别:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
GIL锁:提起python多线程就不得不提一下GIL(Global Interpreter Lock 全局解释器锁),这是目前占统治地位的python解释器CPython中为了保证数据安全所实现的一种锁。不管进程中有多少线程,只有拿到了GIL锁的线程才可以在CPU上运行,即时是多核处理器。对一个进程而言,不管有多少线程,任一时刻,只会有一个线程在执行。对于CPU密集型的线程,其效率不仅仅不高,反而有可能比较低。python多线程比较适用于IO密集型的程序。对于的确需要并行运行的程序,可以考虑多进程。

多线程对锁的争夺,CPU对线程的调度,线程之间的切换等均会有时间开销。

线程与进程区别
下面简单的比较一下线程与进程

  • 进程是资源分配的基本单位,线程是CPU执行和调度的基本单位;
  • 通信/同步方式
    • 进程:
      • 通信方式:管道,FIFO,消息队列,信号,共享内存,socket,stream流;
      • 同步方式:PV信号量,管程
    • 线程:
      • 同步方式:互斥锁,递归锁,条件变量,信号量
      • 通信方式:位于同一进程的线程共享进程资源,因此线程间没有类似于进程间用于数据传递的通信方式,线程间的通信主要是用于线程同步。
  • CPU上真正执行的是线程,线程比进程轻量,其切换和调度代价比进程要小;
  • 线程间对于共享的进程数据需要考虑线程安全问题,由于进程之间是隔离的,拥有独立的内存空间资源,相对比较安全,只能通过上面列出的IPC(Inter-Process Communication)进行数据传输;
  • 系统有一个个进程组成,每个进程包含代码段、数据段、堆空间和栈空间,以及操作系统共享部分 ,有等待,就绪和运行三种状态;
  • 一个进程可以包含多个线程,线程之间共享进程的资源(文件描述符、全局变量、堆空间等),寄存器变量和栈空间等是线程私有的;
  • 操作系统中一个进程挂掉不会影响其他进程,如果一个进程中的某个线程挂掉而且OS对线程的支持是多对一模型,那么会导致当前进程挂掉;
  • 如果CPU和系统支持多线程与多进程,多个进程并行执行的同时,每个进程中的线程也可以并行执行,这样才能最大限度的榨取硬件的性能;

线程和进程的上下文切换
进程切换过程切换牵涉到非常多的东西,寄存器内容保存到任务状态段TSS,切换页表,堆栈等。简单来说可以分为下面两步:

  1. 页全局目录切换,使CPU到新进程的线性地址空间寻址;
  2. 切换内核态堆栈和硬件上下文,硬件上下文包含CPU寄存器的内容,存放在TSS中;
    线程运行于进程地址空间,切换过程不涉及到空间的变换,只牵涉到第二步;

使用多线程还是多进程?
CPU密集型:程序需要占用CPU进行大量的运算和数据处理;

I/O密集型:程序中需要频繁的进行I/O操作;例如网络中socket数据传输和读取等;

由于python多线程并不是并行执行,因此较适合与I/O密集型程序,多进程并行执行适用于CPU密集型程序;
在这里插入图片描述

一、多进程

1.0 犯的错误!

  1. 在无法进入到进程的子进程function里面的时候的原因:
    1. 在pool.apply_async(function, args)中的args参数元祖中,只有一个参数,但是参数后面没有加逗号。
    2. 多进程函数内部中调用了 redis!!!
    3. 进程中的参数队列在创建的时候没有用Manager进行管理。
    4. 使用了进程中的event()函数
  2. 全局和局部问题——多线程会有 全局问题 可以声明global进行通信(线程本质–时间跳变且同一块内存决定);而多进程见下一条。
    1. 全局函数和局部函数报错问题:https://blog.csdn.net/Dontla/article/details/103404534
  3. 共享变量/通信 方法:
    1. 多线程可以使用全局变量,多进程不可以global 只能读不能写!
    2. 加锁共同访问共享变量;https://www.cnblogs.com/lsdb/p/10815319.html
    3. 推荐:基于 本节1.2基础使用 线程池Pool基础上——使用直接返回值t.get() https://www.cnblogs.com/kaituorensheng/p/4465768.html;
    4. 然而,一个个的使用Process返回的只是 当前的进程id。
    5. 或推荐:使用参数中带有multiprocessing.Manager().list()方式添加数值, https://www.cnblogs.com/lsdb/p/10815319.html
  4. 另一个常用的方法:pool.map

其实,还有一种写法,使用pool.map,语法如下:

pool.map(func,iterator) 比如:

pool.map(self.get_kernel, NODE_LIST)

注意:func是一个方法,iterator是一个迭代器。比如:list就是一个迭代器

使用map时,func只能接收一个参数。这个参数就是,遍历迭代器的每一个值。

  1. 但是如果 pool.map 我们想接受多个参数方法——使用偏函数 from functools import partial;都是一并传入迭代数据并按照顺序返回所需要的数据的。

    5.1. 偏函数partial
    偏函数是python自带的包,直接导入就能用。
    偏函数partial的第一个参数就是所承载的原函数,之后原函数的参数再依次传入partial函数。
    例子

# -*- coding: utf-8 -*-
from functools import partial


def calsum(a, b):
    return a + b

# 承载calsum函数,并传入第一个参数
para = partial(calsum, 3)

# 传递第二个参数,就是把2传给para
res = para(2)

# 输出最后的结果
print(res)

# 5
5.2  pool.map应用

举个例子说明:
首先先定义一个列表,里面存放着整数,之后计算这个列表的均值,用多进程判断列表里的每个数字与均值的大小,比均值大输出1,反之输出0.

# -*- coding: utf-8 -*-
from multiprocessing import Pool
# 导入偏函数
from functools import partial
import numpy as np


def adjust(mean, number):
    if number > mean:
        return 1
    else:
        return 0


if __name__ == "__main__":
    num_list = [12, 45, 67, 88, 99, 62]
    # 计算均值
    num_mean = np.mean(num_list)
    # 定义进程池
    pool = Pool(4)
    # 定义偏函数,并传入均值
    pfunc = partial(adjust, num_mean)
    # 执行map,传入列表
    res_list = pool.map(pfunc, num_list)
    print(res_list)

输出结果:

[0, 0, 1, 1, 1, 0]

1.1 介绍

由于GIL(全局解释器)的存在,该机制限制每个python进程中有且只有一个线程同时运行,也就是说即使写了threading,也于事无补,cpu只是在多个thread之间来回跳而已,并没有并发执行这些thread。所以以后要写并发机制,还得用多进程multiprocessing模块。

1.2 我的基础使用使用

使用多进程的四种方式:https://blog.csdn.net/fei347795790/article/details/89737851

方式一: 使用multiprocessing模块: 创建Process的实例,传入任务执行函数作为参数

# -*- coding:utf-8 -*-
"""
Process常用属性与方法:
  name:进程名
  pid:进程id
  run(),自定义子类时覆写
  start(),开启进程
  join(timeout=None),阻塞进程
  terminate(),终止进程
  is_alive(),判断进程是否存活
"""
import os,time
from multiprocessing import Process
def worker():
  print("子进程执行中>>> pid={0},ppid={1}".format(os.getpid(),os.getppid()))
  time.sleep(2)
  print("子进程终止>>> pid={0}".format(os.getpid()))
def main():
  print("主进程执行中>>> pid={0}".format(os.getpid()))
  ps=[]
  # 创建子进程实例
  for i in range(2):
    p=Process(target=worker,name="worker"+str(i),args=())
    ps.append(p)
  # 开启进程
  for i in range(2):
    ps[i].start()
  # 阻塞进程
  for i in range(2):
    ps[i].join()
  print("主进程终止")
if __name__ == '__main__':
  main()

我的:结合 每个Process和其返回值做Queue的添加(Queue不能添加太多!)

class MyClass(multiprocessing.Process):


    def __init__(self, arr, queue):
        super().__init__()
        self.arr = arr
        self.queue = queue

    def run(self):
        self.calPow()


def calPow(arr, queue):
    print('父进程id:', os.getppid(), '\t当前子进程id:', os.getpid(), '\n')
    res = []
    for i in arr:
        res.append(i * i)
    # 将结果放入队列
    queue.put(res)


def main():
    # 生成4个长度为5的list, 等会用4个进程分别计算这4个list中元素的平方
    arrs = [[random.randint(1, 15) for x in range(5)] for x in range(4)]
    jobs = []
    print('\n打印4个list:')
    for i in range(4):
        print(arrs[i])


    # 使用4个进程
    print('\n调用4个子进程:')
    queues = []
    for i in range(4):
        # 使用多进程时,一般使用消息机制实现进程间通信
        # 每个进程都有一套自己的内存, 所以在子进程中创建的容器没法直接传回子进程, 只能用多进程模块提供的队列或者管道
        queue = multiprocessing.Queue()
        queues.append(queue)
        t = multiprocessing.Process(target=calPow, args=(arrs[i], queue))
        jobs.append(t)
        t.start()
    result = []
    # 获取每个进程的结果
    # print('\n获取每个进程的结果')
    for i, t in enumerate(jobs):
        # 等待子进程完成
        t.join()
        # 获取子进程的结果
        # result.append(queues[i].get())
        result+=queues[i].get()


    print('\n打印最终结果:')
    for i in result:
        print(i)

if __name__ == '__main__':
    main()
#########output
打印最终结果:
81
36
169
225
4
1
16
144
25
196
169
81
16
49
4
144
36
144
225
100

或者结合multiprocessing.Manager().list() 做参数
在这里插入图片描述
会返回错误…所以加list[]

	jobs = []
    # queues = []
    lists = []
    processor = len(train_files_path)
    features_train = []
    for i in range(4):
        # 使用多进程时,一般使用消息机制实现进程间通信
        # 每个进程都有一套自己的内存, 所以在子进程中创建的容器没法直接传回子进程, 只能用多进程模块提供的队列或者管道
        # queue = multiprocessing.Queue()
        manager__list = multiprocessing.Manager().list()
        # queues.append(queue)
        lists.append(manager__list)
        t = multiprocessing.Process(target=ext_file_feature1, args=(train_files_path[i], manager__list))
        jobs.append(t)
        t.start()

    print("now 0")
    # 获取每个进程的结果
    # print('\n获取每个进程的结果')
    for i, t in enumerate(jobs):
        # 等待子进程完成
        print("now 1")
        print("i:{}, t:{}".format(i, t))
        t.join()
        # 获取子进程的结果
        # result.append(queues[i].get())
        print("now 2")
        print("i:{}, t:{}".format(i, t))
        # features_train += queues[i].get()
        features_train += lists[i]
        t.close()

    # print(features_train)
    print("Feature length train is: ", len(features_train))
    train_df = pd.DataFrame(features_train)
    train_df = train_df[names]
    print(train_df.head(30))

方式二: 使用multiprocessing模块: 派生Process的子类,重写run方法

# -*- coding:utf-8 -*-
import os,time
from multiprocessing import Process
class MyProcess(Process):
  def __init__(self):
    Process.__init__(self)
  def run(self):
    print("子进程开始>>> pid={0},ppid={1}".format(os.getpid(),os.getppid()))
    time.sleep(2)
    print("子进程终止>>> pid={}".format(os.getpid()))
def main():
  print("主进程开始>>> pid={}".format(os.getpid()))
  myp=MyProcess()
  myp.start()
  # myp.join()
  print("主进程终止")
if __name__ == '__main__':
  main()

对应使用

import multiprocessing
import random
import os


class MyClass(multiprocessing.Process):

    def __init__(self, arr, queue):
        super().__init__()
        self.arr = arr
        self.queue = queue

    def calPow(self):
        print('父进程id:', os.getppid(), '\t当前子进程id:', os.getpid(), '\n')
        res = []
        for i in self.arr:
            res.append(i * i)
        # 将结果放入队列
        self.queue.put(res)

    def run(self):
        self.calPow()


def main():
    # 生成4个长度为5的list, 等会用4个进程分别计算这4个list中元素的平方
    arrs = [[random.randint(1, 15) for x in range(5)] for x in range(4)]
    jobs = []
    print('\n打印4个list:')
    for i in range(4):
        print(arrs[i])

    # 使用4个进程
    print('\n调用4个子进程:')
    queues = []
    for i in range(4):
        # 使用多进程时,一般使用消息机制实现进程间通信
        # 每个进程都有一套自己的内存, 所以在子进程中创建的容器没法直接传回子进程, 只能用多进程模块提供的队列或者管道
        queue = multiprocessing.Queue()
        queues.append(queue)
        t = MyClass(arrs[i], queue)
        jobs.append(t)
        t.start()
    result = []
    # 获取每个进程的结果
    # print('\n获取每个进程的结果')
    for i, t in enumerate(jobs):
        # 等待子进程完成
        t.join()
        # 获取子进程的结果
        result.append(queues[i].get())

    print('\n打印最终结果:')
    for i in result:
        print(i)


if __name__ == '__main__':
    main()

方式三: 使用进程池Pool

# -*- coding:utf-8 -*-
import os,time
from multiprocessing import Pool
def worker(arg):
  print("子进程开始执行>>> pid={},ppid={},编号{}".format(os.getpid(),os.getppid(),arg))
  time.sleep(0.5)
  print("子进程终止>>> pid={},ppid={},编号{}".format(os.getpid(),os.getppid(),arg))
def main():
  print("主进程开始执行>>> pid={}".format(os.getpid()))
  ps=Pool(5)
  for i in range(10):
    # ps.apply(worker,args=(i,))     # 同步执行
    ps.apply_async(worker,args=(i,)) # 异步执行
  # 关闭进程池,停止接受其它进程
  ps.close()
  # 阻塞进程
  ps.join()
  print("主进程终止")
if __name__ == '__main__':
  main()

我的使用返回推荐:

import multiprocessing
import time

def func(msg):
    print("msg:", msg)
    time.sleep(3)
    test = []
    test+=msg
    print("end")
    return test

if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=4)
    result = []
    msg = []
    for i in range(3):
        msg.append(i*2)
        result.append(pool.apply_async(func, (msg, )))
    pool.close()
    pool.join()

    final = []
    for res in result:
        final += res.get()
        # print (":::", res.get())
    print(final)
    print ("Sub-process(es) done.")
########################output:
msg: [0, 2, 4]
msg: [0, 2, 4]
msg: [0, 2, 4]
end
end
end
[0, 2, 4, 0, 2, 4, 0, 2, 4]
Sub-process(es) done.

最终推荐 结合Pool 和 managelist 使用

	## 开始生成 trian的正负样本
    # 定义一个缓存item信息的缓存区
    user_info = {}
    train_df_list = []  # 存放最新生成的df/dict
    train_label_ = train_df[train_df['label'] == 1]
    jobs = []
    p = multiprocessing.Pool(4)
    manager_list_train = multiprocessing.Manager().list()
    for v in tqdm(train_label_.values[0:30]):
        # t = p.apply_async(gene_pos_neg_sample1, args=(manager_list_train, v.tolist()))
        info = user_info.get(v[0], None)
        if info is None:
            info = json.loads(pool.get(str(v[0])))
            user_info[v[0]] = info

        t = p.apply_async(gene_test_test, args=(user_info, manager_list_train, list(train_df['item_id'].unique()), v.tolist()))
        # t = p.apply_async(gene_test, args=(v.tolist(), ))
        # gene_pos_neg_sample(user_info, train_df, train_df_list, pool, v)
        jobs.append(t)
    p.close()
    p.join()

    # for res in jobs:
    #     train_df_list.append(res.get())
        # print(res.get())
    # print(train_df_list)
    # train_df_list += manager_list_train
    print(manager_list_train)
    train_df_list += manager_list_train
    print("train 生成正负样本结束...")
    train_df_list = pd.DataFrame(train_df_list)
    train_df_list.head(30)

最基础

import time
import os
import csv
from multiprocessing import Pool
path = "/2014_test/"
files = os.listdir(path)
def function(i):
	list=[]
	with open(path+str(i),encoding="utf-8") as f :
		reader=csv.reader(f)
		for row in reader:
			list.append(row)
	print(len(list))
	print(time.ctime())
if __name__=="__main__":
	p=Pool(4)
	for i in files:
		p.apply_async(function,args=(i,))
	p.close()
	p.join()

1.3 返回值/通信——见 本节1.0错误 No.3

返回值参考:

注意: 使用 list/dict 可以在manager中 直接使用;而使用 Queue时加入进去——会有size限制!参考:https://www.zhihu.com/question/63265466

二、多线程

2.0 犯的错误!

2.1 介绍

看了一遍排在前面的答案,类似”进程是资源分配的最小单位,线程是CPU调度的最小单位“这样的回答感觉太抽象,都不太容易让人理解。

做个简单的比喻:进程=火车,线程=车厢

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)—— “互斥锁”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)——“信号量”

2.2 我的基础使用

菜鸟官网的使用:https://www.runoob.com/python3/python3-multithreading.html


import threading
import time


def f(i):
    # global n
    # global d
    time.sleep(1)
    print("begin += 1:  i=%s,d=%s" % (i, d_list))
    count = 0
    for j in range(i+1):
        count += j
    # d[i] = count
    d_list.append(count)
    print("end +=1:     i=%s,d=%s" % (i, d_list))


if __name__ == "__main__":
    t_list = []
    n = 0
    d_list = []
    for i in range(10):
        t = threading.Thread(target=f, args=(i, ))
        t.start()
        t_list.append(t)

    for i in t_list:
        i.join()

    print(d_list)
#####输出:
[3, 45, 28, 21, 1, 15, 10, 0, 36, 6]

最基础使用

import operator
import csv
import time
import threading
from time import ctime

def read_file(filpos,i):
    with open(filpos+str(i)+".csv") as f:
        reader=csv.reader(f)
        for i in reader:
            print(i)

threads = []
x=0
for t in range(0,3):
    t= threading.Thread(target=read_file,args=("D:/zhihu/",x))
    threads.append(t)
    x+=1
#join在里面时候只有第一个子进程结束才能打开第二个进程,if__name__ 调用时不可用
if __name__=="__main__":
    for thr in threads:
        thr.start()
    thr.join()
    print("all over %s"%ctime())

2.3 全局变量/其它关于多线程修改同一个数据

https://www.cnblogs.com/owasp/p/5597164.html

三、参考/待看

  1. 受益匪浅的介绍和对比:https://www.cnblogs.com/yssjun/p/11302500.html
  2. 详解:https://www.cnblogs.com/kaituorensheng/p/4445418.html
  3. multiprocessing 官方文档:https://docs.python.org/zh-cn/3/library/multiprocessing.html
  4. 多线程之间的通信:https://www.cnblogs.com/shenh/p/10825656.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值