python 3.x 线程间通信
线程间通信
- 线程间为什么需要通信???通过爬虫的示例代码进行理解线程间的通信.
- 现在有两个线程,这两个线程必须要合作才能够完成爬取文章的详情页的,Thread1与Thread2两者之间如何协作呢???
get_detail_url()函数爬取文章的列表页,然后并解析出其中的文章详情页的url,将url交给get_detail_html()函数.实际上这两个线程就需要线程间的通信,只有完成线程间的通信,才能爬取文章详情页的数据. - 通过使用Thread对象来创建线程,来完成线程间的通信.
线程间的通信方式:
- 共享变量,就是在代码中声明一个全局变量,然后将这个全局变量在各个线程中使用.
共享变量存在很多问题,最直观的就是共享变量线程安全性是不安全的,因为GIL特性导致共享变量中的数据不是我们预期的数据,是因为这些操作不是线程安全的操作,
所以说为了达到预期的效果,我们必须将这些操作上加一把锁.让它线程之间可以按照我们预定的顺序进行同步,这就是共享变量带来的最大的问题,
所以说共享变量这种方式是实际上并不推荐大家去用作我们的通信,除非大家对这个锁足够的了解 - 推荐一种线程间的通信方法就能摆脱这种情况,就是通过queue的方法来完成线程间的通信.`
通过queue的方式进行线程间同步
- Queue的内部实现使用了Condition.
- 使用消息队列Queue来实现
- Queue本身就是一个线程安全的,对Queue进行get()操作的时候,比如多个线程从Queue中取数据的时候,Queue不会使得我们多个线性去操作同一个Queue的时候,造成数据的错误.
- Queue是如何做到线程安全???
- 通过查看Queue的源码,get()和put()两个方法是线程安全的操作.
- 通过get()方法来看一下,get()内部实现是用到了线程同步锁的机制,通过锁做了很多的判断.但是在执行item = self._get()这行语句时,_get()方法并没有加锁.
- _get()方法的实现: def _get(self):
return self.queue.popleft()
- _get()方法的实现: def _get(self):
- 直接使用self.queue,而self.queue = deque().deque()是python中的双端队列,关键是双端队列是线程安全的.
deque()是在字节码的级别就已经达到线程安全.所以说内部的Queue,也就是线程里面的Queue,实际上内部还是使用了deque()
.因为deque()本身是线程安全的,所以说Queue直接使用双端队列.不再需要在_get()方法上面加锁了.这样的话,Queue的性能也会比较高 - Queue里面的方法全都是线程安全的:
- put(self, item, block=True, timeout=None) ---> 往队列放数据
- block: 是否阻塞
- block为false时,如果消息队列有空间就直接把数据放进去,如果没有空间的话,直接抛出空异常
- timeout: 如果我们的队列已满,put()方法就会内阻塞,put()方法就会一直等待,但是在某些情况下,并不希望我们一直等待下去,这样会将我们整个程序block(阻塞)住,所以说我们可以通过设置timeout值,这个timeout结束之前对列还没有位置的话,抛出空异常
- get(self, block=True, timeout=None) ---> 从队列取数据
- block: 是否阻塞
- get()方法是阻塞的方法,如果队列queue为空,会一直停在queue.get()这行代码上,符合我们的预期,然后取到之后,后面就可以执行后面的代码
- put_nowait(self,item)
- 在往队列里面放数据的时候,我们没有必要等待到他完成后在返回,put_nowait()是一个异步的方法,他的内部实现使用了put()方法
- get_nowait(self)
- qsize() 获取Queue队列的长度
- empty() 判断Queue队列是否为空
- full() 判断Queue队列是否已满,如果队列已满,put()方法会被阻塞,等到队列有空闲空间为止,才能够成功执行代码
- join(),task_done()
- 一直block住,直到我们的queue接受到一个task_done这么一个信号,join()方法从Queue的角度可以让我们主线程阻塞.
- join()函数想退出,我们必须要在某一个地方,给这个Queue发一个task_done,join()方法才会退出,不然的话,join()是一直不会退出的,只有join()函数退出,我们的主线程才能退出.
- 正是因为join()和task_done()是成对的,所以说在某些时候,我们就可以在爬虫里面加一些逻辑,
- 比如说我爬完1000条数据,我就把我整个爬虫给停掉,于是我就调用task_done()函数,我们主线程就能退出.
- put(self, item, block=True, timeout=None) ---> 往队列放数据
- Queue给我们提供了很多的方便,能够让我们更好的控制多线程,所以说在编程的时候,我们更加建议大家首选用Queue,去完成我们的线程间的一个通信
-
PriorityQueue优先级队列
-
如果希望后插入的数据,能够尽快被获取到,就可以优先考虑使用优先级队列
-
示例模拟伪代码(共享变量)
1 import threading 2 import time 3 4 detail_url_list = [] 5 6 7 def get_detail_url(): 8 """ 9 爬取文章列表页 10 """ 11 global detail_url_list 12 print("get detail url started!") 13 # 网络请求数据 14 time.sleep(4) 15 # 假如第一次爬取20条url 16 for i in range(20): 17 detail_url_list.append("http://projectsedu.com/{id}".format(id=i)) 18 print("get detail url end!") 19 20 21 def get_detail_html(): 22 """ 23 爬取文章详情页 24 """ 25 global detail_url_list 26 while True: 27 if len(detail_url_list): 28 # 将列表中的某个元素移除并返回,默认移除列表最后一个元素,如果列表为空抛出IndexError 29 url = detail_url_list.pop() 30 print("get detail html started!") 31 time.sleep(2) 32 print("get detail html end!") 33 34 35 if __name__ == "__main__": 36 thread_detail_url = threading.Thread(target=get_detail_url) 37 38 start_time = time.time() 39 40 thread_detail_url.start() 41 42 for i in range(10): 43 thread_detail_html = threading.Thread(target=get_detail_html) 44 thread_detail_html.start() 45 46 # thread_detail_url.join() 47 # thread_detail_html.join() 48 49 print("last time: {}".format(time.time() - start_time)) 50 51 """ 52 输出结果: 53 get detail url started! 54 Exception in thread Thread-2: 55 Traceback (most recent call last): 56 File "D:\python3\lib\threading.py", line 916, in _bootstrap_inner 57 self.run() 58 File "D:\python3\lib\threading.py", line 864, in run 59 self._target(*self._args, **self._kwargs) 60 File "F:/C/PythonProject/PythonHighLevelAndIo/test/chapter11/python_queue_01.py", line 48, in get_detail_html 61 url = detail_url_list.pop() 62 IndexError: pop from empty list 63 64 get detail url end! 65 last time: 4.0012288093566895 66 """
示例模拟伪代码(Queue)
1 from queue import Queue 2 import time 3 import threading 4 5 6 def get_detail_url(queue): 7 """ 8 爬取文章列表页 9 """ 10 print("get detail url started!") 11 time.sleep(4) 12 # 假如第一次爬取20条url 13 for i in range(20): 14 queue.put("http://projectsedu.com/{id}".format(id=i)) 15 print("get detail url end!") 16 17 18 def get_detail_html(queue): 19 """ 20 爬取文章详情页 21 """ 22 while True: 23 # get()方法是阻塞的方法,如果队列queue为空,会一直停在queue.get()这行代码上. 24 url = queue.get() 25 print("get detail html started!") 26 time.sleep(2) 27 print("get detail html end!") 28 29 30 if __name__ == "__main__": 31 # 设置最大值为1000,允许消息队列最大有1000个消息. 32 # 因为如果Queue过大的话,实际上对内存会有很大的影响. 33 detail_url_queue = Queue(maxsize=1000) 34 35 thread_detail_url = threading.Thread(target=get_detail_url, args=(detail_url_queue,)) 36 37 start_time = time.time() 38 thread_detail_url.start() 39 40 for i in range(10): 41 thread_detail_html = threading.Thread(target=get_detail_html, args=(detail_url_queue,)) 42 thread_detail_html.start() 43 44 # 必须给Queue发送一个task_done, 45 detail_url_queue.task_done() 46 detail_url_queue.join() 47 48 print("last time: {}".format(time.time() - start_time))
********
posted on 2019-04-24 10:49 jaydenjune 阅读(53) 评论(0) 收藏 举报
浙公网安备 33010602011771号