【注意:】DataFrame.values
返回的是numpy.array(a) ; 如果要相加 train_df_list += a; 需要转化 a.tolist()
!!!
零、引言
0.1 前言
在使用多进程和多线程过程中犯了很多错误;也学到了很多基础技法。
为了方便调试和未来不再犯类似错误;方便进行快速的操作、使用 特记录如下。
0.2 二者选择
-
前言:因为GIL的限制,python的线程是无法真正意义上并行的。相对于异步编程,其性能可以说不是一个等量级的。为什么我们还要学习多线程编程呢,虽然说异步编程好处多,但编程也较为复杂,逻辑不容易理解,学习成本和维护成本都比较高。毕竟我们大部分人还是适应同步编码的,除非一些需要高性能处理的地方采用异步。
-
且由于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,切换页表,堆栈等。简单来说可以分为下面两步:
- 页全局目录切换,使CPU到新进程的线性地址空间寻址;
- 切换内核态堆栈和硬件上下文,硬件上下文包含CPU寄存器的内容,存放在TSS中;
线程运行于进程地址空间,切换过程不涉及到空间的变换,只牵涉到第二步;
使用多线程还是多进程?
CPU密集型:程序需要占用CPU进行大量的运算和数据处理;
I/O密集型:程序中需要频繁的进行I/O操作;例如网络中socket数据传输和读取等;
由于python多线程并不是并行执行,因此较适合与I/O密集型程序,多进程并行执行适用于CPU密集型程序;
一、多进程
1.0 犯的错误!
- 在无法进入到进程的子进程
function
里面的时候的原因:
1. 在pool.apply_async(function, args)中的args参数元祖中,只有一个参数,但是参数后面没有加逗号。
2. 多进程函数内部中调用了redis
!!!
3. 进程中的参数队列在创建的时候没有用Manager进行管理。
4. 使用了进程中的event()函数 - 全局和局部问题——多线程会有 全局问题 可以声明global进行通信(线程本质–时间跳变且同一块内存决定);而多进程见下一条。
1. 全局函数和局部函数报错问题:https://blog.csdn.net/Dontla/article/details/103404534 - 共享变量/通信 方法:
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 - 另一个常用的方法:
pool.map
其实,还有一种写法,使用pool.map,语法如下:
pool.map(func,iterator) 比如:
pool.map(self.get_kernel, NODE_LIST)
注意:func是一个方法,iterator是一个迭代器。比如:list就是一个迭代器
使用map时,func只能接收一个参数。这个参数就是,遍历迭代器的每一个值。
-
但是如果
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