参考文章:
Python多线程3 多线程的传参与返回值
BBJG_001的博客——多线程
threading库知识点补充
学习网站:
莫烦Python
在主线程用一个变量或者直接输出就能获取或使用函数中return的值。但是在多线程中,可以这么理解,多线程之间的变量空间是互相隔绝的,所以return是不能把值返回到主进程的,只能在所在的线程使用,线程结束,值所在空间也就被释放了。所以,多线程之间需要一个更加全局性的存储器来保存所有线程之间的值,这里使用queue(队列)来完成这项工作。
什么是队列?
简单介绍一下队列,队列是一种存储结构,就像一个水管一样,给这根水管规定一个方向,只能从一头写入(进水),只能从另一头读出(出水)。
存满了怎么办?
采用动态机制,每存一项,我找一块空间,标记好上一次存储的队列头部;然后把新存入的这项声明为整个队列新的头部。从逻辑上讲,这还是那个队列,动态的增加了一节长度(通过地址指向连接的方式),所以,它是不会满的。(不考虑物理存储空间不够的情况)
读出来在队列中就没了。
下面程序中从队列中读出的命令是q.get(),这个函数不用传参,不像列表那样通过索引取值。上面也提到了,队列只能从一端取值,每次只能取尾部的一个值,这里的取值动作相当于 拿出来,之后尾部会向前移动一个单位,这样也就再能取下一个值了,通过这样的机制,使队列成为了一种先进先出的数据结构。
示例程序:
import threading
import time
from queue import Queue
# def job(l, q):
# for i in range(len(l)):
# l[i] = l[i]**2
# return l # 在普通的函数定义时,使用return来返回值
def job(l, q):
for i in range(len(l)):
l[i] = l[i]**2
q.put(l) # 在多线程中,即使return了,也只能return到当前线程,无法给主线程调用,因此这里用一个更加全局性的存储器——queue(队列)——来存储多个线程的结果
# time.sleep(1)
def multithreading():
q = Queue()
threads = []
data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
for i in range(4): # for1
t = threading.Thread(target=job, args=(data[i], q))
# 注意这里target属性只用来指定调用的函数的名称,没有后面的括号
# args属性用来传递参数
t.start()
threads.append(t)
# 至于下面的两个循环,实践证明是可以合并的
for thread in threads: # for2
thread.join()
results = []
for x in range(4): # for3
results.append(q.get()) # 这里为什么不能写入上面的循环,如果单个线程执行的慢,q.get()是无法取到值的
# 合并写法
# results = []
# for thread in threads:
# thread.join()
# results.append(q.get())
print(results)
if __name__ == '__main__':
multithreading()
直接return是不行滴,用一个全局queue(队列)来实现值传递
导入支持包from queue import Queue
声明一个queueq = Queue()
向队列中存值q.put(值)
从队列中取值q.get()
上面的程序就是每个线程处理一段数据,将处理结果存入队列,所有线程都结束之后,从队列中依次取出各线程的操作结果,并print出来。
会发现上面的程序中写了三个for循环,其循环结构都是一样的,都是针对4个线程,每轮循环处理一个线程,但是它们确实是不能合在一块的,至少for2和for3是不能放在for1中的。
如果把for2中的.join()加到for1中,第一轮第一个线程.join之后,后面的会等第一个线程结束之后才会执行,则在第一个线程完成之前不会去生成第二个线程的,同理,后面的线程都会等待前面的线程完成之后才会生成。这样程序就变成了串行的(为了看到效果可以在job()函数中做一段时间延迟(time.sleep()),就会发现把.join()加到for1中,执行时间变成了原来的4倍(因为有4个线程串行)),多线程也就没有了意义。
如果把for3的代码加到for1中,results.append(q.get())是主线程的操作,它不会等待非主线程(上面刚提到for1中不能.join()),如果非主线程耗时比较长,则会发生队列还没有被写入(q.put(值))就已经开始读(q.get())的错误。
测试了for2和for3合并起来是不会影响效率的,但是也只局限于这个程序,实际上这样搞也是不科学的。在这个程序中,results.append()每次append的都是这一轮线程的,如果要操作到之后线程的结果,因为之后的线程没有.join(),就有可能发生取不到值的情况。
综上,写成三个for还是很有必要的。也就是如果要操作多个线程的结果,严格分成 线程声明执行、进程.join()、操作多个进程结果三步才是安全且有必要的。
import threading
import math
import time
def one_list_to_more(items, n):
return [items[i:i + n] for i in range(0, len(items), n)]
#多线程进入不同的药品详情页拿低价商品
def multi_threading(new_goods_id_list):
number = len(new_goods_id_list)
n = math.ceil(number/5) #5,是为了分为5组
# 将商品集合转为二维列表,用于控制并发循环
_get_goods_info_list = one_list_to_more(new_goods_id_list, n)
thread_list = []
# 取每个小列表的商品获取详情
for items_list in _get_goods_info_list:
t = threading.Thread(target=run_main, args=(items_list))
thread_list.append(t)
for t in thread_list:
t.setDaemon(True) # 设置为守护线程,不会因主线程结束而中断
t.start()
for t in thread_list:
t.join() # 子线程全部加入,主线程等所有子线程运行完毕
print('主线程结束\n')
def run_main(*arg):
threadname = threading.currentThread().getName()
print('开始线程:', threadname + '//' + '%s' % time.ctime(time.time()))
print(*arg)
print(arg)
print(list(arg))
print('线程结束:', threadname + '//' + '%s' % time.ctime(time.time()))
new_goods_id_list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
multi_threading(new_goods_id_list)
运行结果:
开始线程: Thread-1//Fri Aug 21 19:23:07 2020
1 2 3 4
(1, 2, 3, 4)
[1, 2, 3, 4]
开始线程:线程结束: Thread-1//Fri Aug 21 19:23:07 2020
Thread-2//Fri Aug 21 19:23:07 2020
5 6 7 8
(5, 6, 7, 8)
[5, 6, 7, 8]
线程结束: Thread-2//Fri Aug 21 19:23:07 2020
开始线程: Thread-3//Fri Aug 21 19:23:07 2020
9 10 11 12
(9, 10, 11, 12)
[9, 10, 11, 12]
线程结束: Thread-3//Fri Aug 21 19:23:07 2020开始线程:
Thread-4//Fri Aug 21 19:23:07 2020
13 14 15 16
(13, 14, 15, 16)
[13, 14, 15, 16]
线程结束: Thread-4//Fri Aug 21 19:23:07 2020
开始线程: Thread-5//Fri Aug 21 19:23:07 2020
17 18 19 20
(17, 18, 19, 20)
[17, 18, 19, 20]
线程结束: Thread-5//Fri Aug 21 19:23:07 2020
主线程结束
代码:
import threading
import math
import time
from queue import Queue
def one_list_to_more(items, n):
return [items[i:i + n] for i in range(0, len(items), n)]
#多线程进入不同的药品详情页拿低价商品
def multi_threading(new_goods_id_list):
number = len(new_goods_id_list)
n = math.ceil(number/5) #5,是为了分为5组
# 将商品集合转为二维列表,用于控制并发循环
_get_goods_info_list = one_list_to_more(new_goods_id_list, n)
thread_list = []
q = Queue()
m = 2
# 取每个小列表的商品获取详情
for items_list in _get_goods_info_list:
t = threading.Thread(target=run_main, args=(items_list))
thread_list.append(t)
for t in thread_list:
t.setDaemon(True) # 设置为守护线程,不会因主线程结束而中断
t.start()
for t in thread_list:
t.join() # 子线程全部加入,主线程等所有子线程运行完毕
print('主线程结束\n')
def run_main(items_list):
threadname = threading.currentThread().getName()
print('开始线程:', threadname + '//' + '%s' % time.ctime(time.time()))
# print(*arg)
print(items_list)
for i in range(len(items_list)):
items_list[i] = items_list[i] **2
print(items_list)
#print(m) # 在
print('线程结束:', threadname + '//' + '%s' % time.ctime(time.time()))
new_goods_id_list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
multi_threading(new_goods_id_list)
运行错误:
Exception in thread Thread-2:
Traceback (most recent call last):
File "C:\Python\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: run_main() takes 1 positional argument but 4 were given
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: run_main() takes 1 positional argument but 4 were given
Exception in thread Thread-5:
Traceback (most recent call last):
File "C:\Python\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: run_main() takes 1 positional argument but 4 were given
Exception in thread Thread-3:
Traceback (most recent call last):
File "C:\Python\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: run_main() takes 1 positional argument but 4 were given
Exception in thread Thread-4:
Traceback (most recent call last):
File "C:\Python\lib\threading.py", line 926, in _bootstrap_inner
self.run()
File "C:\Python\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
TypeError: run_main() takes 1 positional argument but 4 were given
主线程结束
对其中传参进行改动:
代码:
import threading
import math
import time
from queue import Queue
def one_list_to_more(items, n):
return [items[i:i + n] for i in range(0, len(items), n)]
#多线程进入不同的药品详情页拿低价商品
def multi_threading(new_goods_id_list):
number = len(new_goods_id_list)
n = math.ceil(number/5) #5,是为了分为5组
# 将商品集合转为二维列表,用于控制并发循环
_get_goods_info_list = one_list_to_more(new_goods_id_list, n)
thread_list = []
q = Queue()
m = 2
# 取每个小列表的商品获取详情
for items_list in _get_goods_info_list:
t = threading.Thread(target=run_main, args=(items_list,m))
thread_list.append(t)
for t in thread_list:
t.setDaemon(True) # 设置为守护线程,不会因主线程结束而中断
t.start()
for t in thread_list:
t.join() # 子线程全部加入,主线程等所有子线程运行完毕
print('主线程结束\n')
def run_main(items_list,m):
threadname = threading.currentThread().getName()
print('开始线程:', threadname + '//' + '%s' % time.ctime(time.time()))
# print(*arg)
print(items_list)
for i in range(len(items_list)):
items_list[i] = items_list[i] **2
print(items_list)
print(m) # 在
print('线程结束:', threadname + '//' + '%s' % time.ctime(time.time()))
new_goods_id_list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
multi_threading(new_goods_id_list)
运行结果:
开始线程: Thread-1//Fri Aug 21 19:39:19 2020
[1, 2, 3, 4]
[1, 4, 9, 16]
2
线程结束: Thread-1//Fri Aug 21 19:39:19 2020
开始线程: Thread-2//Fri Aug 21 19:39:19 2020
[5, 6, 7, 8]
[25, 36, 49, 64]
2
线程结束: Thread-2//Fri Aug 21 19:39:19 2020
开始线程: Thread-3//Fri Aug 21 19:39:19 2020
[9, 10, 11, 12]
[81, 100, 121, 144]
2
线程结束: Thread-3//Fri Aug 21 19:39:19 2020
开始线程: Thread-4//Fri Aug 21 19:39:19 2020
[13, 14, 15, 16]
[169, 196, 225, 256]
2
线程结束: Thread-4//Fri Aug 21 19:39:19 2020
开始线程: Thread-5//Fri Aug 21 19:39:19 2020
[17, 18, 19, 20]
[289, 324, 361, 400]
2
线程结束: Thread-5//Fri Aug 21 19:39:19 2020
主线程结束
再次改动代码:
import threading
import math
import time
def one_list_to_more(items, n):
return [items[i:i + n] for i in range(0, len(items), n)]
#多线程进入不同的药品详情页拿低价商品
def multi_threading(new_goods_id_list):
number = len(new_goods_id_list)
n = math.ceil(number/5) #5,是为了分为5组
# 将商品集合转为二维列表,用于控制并发循环
_get_goods_info_list = one_list_to_more(new_goods_id_list, n)
thread_list = []
m = 2
# 取每个小列表的商品获取详情
for items_list in _get_goods_info_list:
t = threading.Thread(target=run_main, args=(items_list))
thread_list.append(t)
for t in thread_list:
t.setDaemon(True) # 设置为守护线程,不会因主线程结束而中断
t.start()
for t in thread_list:
t.join() # 子线程全部加入,主线程等所有子线程运行完毕
print('主线程结束\n')
def run_main(*items_list):
threadname = threading.currentThread().getName()
print('开始线程:', threadname + '//' + '%s' % time.ctime(time.time()))
print(*items_list)
print(items_list)
items_list = list(items_list)
for i in range(len(items_list)):
items_list[i] = items_list[i] **2
print(items_list)
print('线程结束:', threadname + '//' + '%s' % time.ctime(time.time()))
new_goods_id_list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
multi_threading(new_goods_id_list)
运行结果:
开始线程: Thread-1//Fri Aug 21 19:47:04 2020
1 2 3 4
(1, 2, 3, 4)
[1, 4, 9, 16]
线程结束: 开始线程:Thread-1//Fri Aug 21 19:47:04 2020
Thread-2//Fri Aug 21 19:47:04 2020
5 6 7 8
(5, 6, 7, 8)
[25, 36, 49, 64]
线程结束: Thread-2//Fri Aug 21 19:47:04 2020
开始线程: Thread-3//Fri Aug 21 19:47:04 2020
9 10 11 12
(9, 10, 11, 12)
[81, 100, 121, 144]
线程结束: 开始线程:Thread-3//Fri Aug 21 19:47:04 2020
Thread-4//Fri Aug 21 19:47:04 2020
13 14 15 16
(13, 14, 15, 16)
[169, 196, 225, 256]
线程结束: Thread-4//Fri Aug 21 19:47:04 2020
开始线程: Thread-5//Fri Aug 21 19:47:04 2020
17 18 19 20
(17, 18, 19, 20)
[289, 324, 361, 400]
线程结束: Thread-5//Fri Aug 21 19:47:04 2020
主线程结束