Python爬虫笔记——多线程(threading)传参

参考文章:
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
主线程结束

你可能感兴趣的:(爬虫笔记,python,多线程)