Python建立多线程任务并获取每个线程返回值
1.进程和线程
(1)进程是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。进程也可以派生新的进程来执行其他任务,不过每个新进程都拥有自己的内存和数据栈,所以只能采用进程间通信(IPC)的方式共享信息。
(2)线程与进程类似,不过他们是在同一个进程下执行的,并共享相同的上下文。线程一般是以并发方式执行的,但是在单核CPU中真正的并发是不可能的,:每个线程运行一小会儿,然后让步给其他线(再次排队等待更多的CPU时间)。但是,多线程访问同一片数据,由于访问的顺序不同可能导致结构不一致。例如append(0-1)和print会同时有01
PS:内存中可以有许多程序,但是在任意给定时刻只能有一个程序在运行。同理,尽管Python 解释器中可以运行多个线程,但是在任意给定时刻只有一个线程会被解释器执行。
2.线程
线程相关的模块有thread和threading,其中threading是thread的改进和升级版,且thread模块有一个致命的缺点,在主线程退出之后,所有其他线程都会在没有清理的情况下直接退出。threading模块中加入守护线程概念,如果被指明为守护线程后主线程退出后不会等待守护线程执行完毕才吐出。整个Python 程序(可以解读为:主线程)将在所有非守护线程退出之后才退出,换句话说,就是没有剩下存活的非守护线程时。
主线程和子线程分别是什么?举例?
而主线程应该做一个好的管理者,负责了解每个单独的线程需要执行什么,每个派生的线程需要哪些数据或参数,这些线程执行完成后会提供什么结果。这样,主线程就可以收集每个线程的结果,然后汇总成一个有意义的最终结果。
(1)单线程:
我需要做两件事,只能做完一件再做第二件,排好队
# -*-coding:utf-8-*- from time import ctime, sleep import threading loops = [4, 2] def loop(nloop,nsec): print('start loop', nloop, 'at :', ctime()) sleep(nsec) print('done loop', nloop, 'at:', ctime()) def main(): print('start at',ctime()) nloops = range(len(loops)) for i in nloops: loop(i, loops[i]) print('DONE AT:', ctime()) if __name__ == '__main__': main()
返回结果:
start at Sun Dec 3 12:10:52 2017
start loop 0 at : Sun Dec 3 12:10:52 2017
done loop 0 at: Sun Dec 3 12:10:56 2017
start loop 1 at : Sun Dec 3 12:10:56 2017
done loop 1 at: Sun Dec 3 12:10:58 2017
DONE AT: Sun Dec 3 12:10:58 2017
(2) 多线程:(建立threads实例,传给他一个函数)
# -*-coding:utf-8-*- from time import ctime, sleep import threading loops = [4, 2] def loop(nloop,nsec): print('start loop', nloop, 'at :', ctime()) sleep(nsec) print('done loop', nloop, 'at:', ctime()) def main(): print('start at',ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=loop,args=(i,loops[i])) threads.append(t) for i in nloops: # start threads 此处并不会执行线程,而是将任务分发到每个线程,同步线程。等同步完成后再开始执行start方法 threads[i].start() for i in nloops: # jion()方法等待线程完成 threads[i].jion() print('DONE AT:', ctime()) if __name__ == '__main__': main()
运行结果:
start at Sun Dec 3 12:08:23 2017
start loop 0 at : Sun Dec 3 12:08:23 2017
start loop 1 at : Sun Dec 3 12:08:23 2017
done loop 1 at: Sun Dec 3 12:08:25 2017
done loop 0 at: Sun Dec 3 12:08:27 2017
DONE AT: Sun Dec 3 12:08:27 2017
可以看到loop0和loop1同时进行,当时间较长的子线程loop0完成后,主线程print('DONE AT:', ctime())开始任务
(3)多线程(创建threads实例,传递给他一个可调用的类实例):
# -*-coding:utf-8-*- from time import ctime, sleep import threading loops = [4, 2] class MyThread(object): def __init__(self, func, args, name=''): self.name = name self.func = func self.args = args def __call__(self): self.func(*self.args) def loop(nloop, nsec): print('start loop', nloop, 'at :', ctime()) sleep(nsec) print('done loop', nloop, 'at:', ctime()) def main(): print('start at',ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = threading.Thread(target=MyThread(loop, (i, loops[i]), loop.__name__)) threads.append(t) for i in nloops: # start threads 此处并不会执行线程,而是将任务分发到每个线程,同步线程。等同步完成后再开始执行start方法 threads[i].start() for i in nloops: # jion()方法等待线程完成 threads[i].join() print('DONE AT:', ctime()) if __name__ == '__main__': main()
join函数的原理就是一次检验线程池中的线程是否结束,没有结束就阻塞直到线程结束。如果结束则就跳转执行下一个线程的join函数。如果不用join(),主线程跑的比子线程快会拿不到结果
(3)通过多线程获取返回值
# -*-coding:utf-8-*- from time import ctime, sleep import threading import numpy as np import collections loops = ['广州', '北京'] t_list = ['01', '02', '03'] cldas_sum = collections.deque() class MyThread(threading.Thread): def __init__(self, func, args, name=''): threading.Thread.__init__(self) self.name = name self.func = func self.args = args self.result = self.func(*self.args) def get_result(self): try: return self.result except Exception: return None def loop(nloop): for j in t_list: cldas_values = [] for k in range(4): cldas_value = nloop + str(k) cldas_values.append(cldas_value) cldas_values.append(j) cldas_values.append(nloop) cldas_sum.append(cldas_values) print(id(cldas_values)) #print(cldas_sum) return cldas_sum def main(): print('start at', ctime()) threads = [] nloops = range(len(loops)) for i in nloops: t = MyThread(loop, (loops[i],), loop.__name__) threads.append(t) for i in nloops: # start threads 此处并不会执行线程,而是将任务分发到每个线程,同步线程。等同步完成后再开始执行start方法 threads[i].start() for i in nloops: # jion()方法等待线程完成 threads[i].join() print(threads[1].get_result()) print('DONE AT:', ctime()) if __name__ == '__main__': main()
写这个脚本的一个目的就是看所有脚本共同调用同一个函数,每个脚本都在这个函数中都有一个相同的变量,那么这个变量会被共用还是每个线程自己各有一个闭包。不过函数内定义的变量是闭包,调用函数时创建,返回时销毁。
最终返回结果如下:
start at Tue Dec 5 10:32:38 2017
728072411976
728072411848
728072411784
728072411656
728072364680
728072364808
deque([['广州0', '广州1', '广州2', '广州3', '01', '广州'], ['广州0', '广州1', '广州2', '广州3', '02', '广州'], ['广州0', '广州1', '广州2', '广州3', '03', '广州'], ['北京0', '北京1', '北京2', '北京3', '01', '北京'], ['北京0', '北京1', '北京2', '北京3', '02', '北京'], ['北京0', '北京1', '北京2', '北京3', '03', '北京']])
DONE AT: Tue Dec 5 10:32:38 2017
需要注意的是:
(1)如果多个线程共用一个公共数据,那么我们需要做的就是将这个公共数据设置成队列格式,要不然多个线程共同访问这个数据可能会出错,需要加锁。设置成队列比加锁再放锁效率高多了
(2)线程之间同一个变量id都不一样,还是不知道是否其他线程会涉足另一个线程