上一篇文章介绍了:创建线程的两种方式、Event对象判断线程是否启动、利用信号量控制线程并发。

博客链接:线程、进程、协程和GIL(二)

这一篇来说说线程间通信的那些事儿:

  一个线程向另一个线程发送数据最安全的方式就是使用queue库中的队列了,通过创建一个供多个线程共享的Queue对象,这些线程使用put()和get()操作来向队列中添加数据或者从队列中取出数据,以达到线程间通信的效果。

  queue队列基本方法:

    queue.Queue(maxsize = num):  FIFO  先进先出队列,如果maxsize小于或等于0 则代表队列长度无线。

    queue.LifoQueue(maxsize = num): LIFO 后进先出队列(类似于栈),如果maxsize小于或等于0 则代表队列长度无线。

    Queue.qsize(): 返回当前队列中元素的个数

   Queue.empty()   如果队列为空,返回True,反之False

   Queue.full()   如果队列满了,返回True,反之False

   Queue.get([block[, timeout]])   读队列,timeout等待时间

   Queue.put(item, [block[, timeout]])   写队列,timeout等待时间

     Queue.queue.clear()   清空队列

  使用Queue构造生产者消费者模型来实现线程间的通信:

import time
from queue import Queue,LifoQueue
from threading import Thread def producer(in_q):
while True:
time.sleep(1)
data = '包子'
if in_q.full() == True:
print('蒸笼满了,放不下了')
in_q.put(data) # 向队列中塞东西
print('小明蒸%s!' %(data)) def customer(out_q):
while True:
time.sleep(3)
if out_q.empty() == True:
print('小红没取到包子,饿死了!')
data = out_q.get() # 从队列中取东西
print('小红取 %s ' % (data)) if __name__ == '__main__':
q = Queue(maxsize=3)
t1 = Thread(target=producer, args=(q,))
t2 = Thread(target=customer, args=(q,))
t1.start()
t2.start()

  上面的代码实现了一个简单的生产者消费者,小明负责蒸包子,小红负责吃包子。当队列被包子塞满时,小明就再也放不进去了,此时生产者这个线程就会阻塞。当小红将队列中的包子吃完时,消费者这个线程就会阻塞。因为Queue对象已经封装了必要的锁,所以我们可以在多个线程之间安全的功能共享数据。但是在生产者消费者的关闭问题会有一些麻烦,通用的解决方式就是在队列中放置一个特殊值,当消费者读到这个值时,就终止执行。

  不过有个问题需要注意:向队列中添加数据项时,并不会复制此数据项,线程间的通信实际上是在线程间传递对象引用。如果你单线对象的共享状态,那么最好只传递不可修改的数据结构(如:整型、字符串、或者元组)或者一个对象的深拷贝。

  给关键部分加锁

  线程的不安全:同一进程里线程是共享数据的,当各个线程访问同一个数据资源时会出现竞争状态,即数据可能会同时被多个线程占用,造成数据混乱,这就是线程的不安全。

  为了保证线程安全,所以引进了互斥锁,确保某段关键代码、共享数据只能由一个线程从头到尾完整地执行:

  显式的加锁:

from threading import Thread, Lock

num = 0
lock = Lock() # 定义一个锁 def run():
global num, lock # 获取全局变量
lock.acquire() # 加锁
num += 1
print(num)
lock.release() # 释放锁 if __name__ == '__main__':
Thread_list = []
for i in range(1000):
t = Thread(target=run)
Thread_list.append(t)
for i in Thread_list:
i.start()

  死锁:但是加入互斥锁之后有可能会产生一个问题:死锁:若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果谁也不愿意先解锁,互相等着,程序无法执行下去,这就是死锁。

  比如:有两个线程一、二,两个共享资源A、B,线程一给资源A加锁,线程二给资源B加锁,然后资源A需要访问资源B,资源B需要调用资源A,线程一二双方都在等待对方释放锁,所以就会造成死锁。

   But、当程序员在加锁之后忘记调用release()方法,或者加锁之后程序抛异常导致不能正常释放锁,有可能会造成死锁,为了避免这种情况,我们不需要显式的手动加锁和释放锁,而是使用with语句来进行自动控制:

  

from threading import Thread, Lock

num = 0
lock = Lock() # 定义一个锁 def run():
global num, lock
with lock: # 自动的控制加锁和释放锁
num += 1
print(num) if __name__ == '__main__':
Thread_list = []
for i in range(1000):
t = Thread(target=run)
Thread_list.append(t)
for i in Thread_list:
i.start()

  创建一个线程池: 

  concurrent.futures 函数库有一个 ThreadPoolExecutor 类可以被用来完成这个任务

from concurrent.futures import ThreadPoolExecutor

def run():
print('我是子线程') if __name__ == '__main__':
pool = ThreadPoolExecutor(max_workers=3) # 创建一个容量为3的线程池
for i in range(3):
t = pool.submit(run,) #在线程池中生成三个线程,他们都来调用run方法
print('我是主线程')

想了解更多Python关于爬虫、数据分析的内容,欢迎大家关注我的微信公众号:悟道Python

  

线程、进程、协程和GIL(三)的更多相关文章

  1. 进程、线程、协程和GIL(二)

    上一篇博客讲了进程.线程.协程和GIL的基本概念,这篇我们来说说在以下三点: 1> python中使用threading库来创建线程的两种方式 2> 使用Event对消来判断线程是否已启动 ...

  2. 学到了林海峰,武沛齐讲的Day34 完 线程 进程 协程 很重要

    线程 进程 协程 很重要 ...儿子满月回家办酒,学的有点慢,坚持

  3. 15.python并发编程(线程--进程--协程)

    一.进程:1.定义:进程最小的资源单位,本质就是一个程序在一个数据集上的一次动态执行(运行)的过程2.组成:进程一般由程序,数据集,进程控制三部分组成:(1)程序:用来描述进程要完成哪些功能以及如何完 ...

  4. Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/selector】

    一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...

  5. 文成小盆友python-num11-(1) 线程 进程 协程

    本节主要内容 线程补充 进程 协程 一.线程补充 1.两种使用方法 这里主要涉及两种使用方法,一种为直接使用,一种为定义自己的类然后继承使用如下: 直接使用如下: import threading d ...

  6. python_21_线程+进程+协程

    python_线程_进程_协程 什么是线程? -- os能够进行运算调度的最小单位,被包含在进程之中,是一串指令的集合 -- 每个线程都是独立的,可以访问同一进程下所有的资源 什么是进程? -- 每个 ...

  7. python 线程 进程 协程 学习

    转载自大神博客:http://www.cnblogs.com/aylin/p/5601969.html 仅供学习使用···· python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和 ...

  8. python之并发编程(线程\进程\协程)

    一.进程和线程 1.进程 假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源.是 ...

  9. python中线程 进程 协程

    多线程:#线程的并发是利用cpu上下文的切换(是并发,不是并行)#多线程执行的顺序是无序的#多线程共享全局变量#线程是继承在进程里的,没有进程就没有线程#GIL全局解释器锁#只要在进行耗时的IO操作的 ...

  10. 线程&进程&协程

    线程 线程是应用程序中工作的最小单元,它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.Threading用 ...

随机推荐

  1. JavaSE之Java基础(2)

    6.java8新特性 Lambda表达式 接口的默认方法与静态方法 方法引用 重复注解 扩展注解的支持 Optional类 Stream API Date Time API JavaScript引擎N ...

  2. vue 2.6 更新变动

    [原文链接] Slots:新语法,性能改进为3.0做准备改用 v-slot 指令 //默认插槽 <baz v-slot ="baz"> {{baz}} </ ba ...

  3. HTML超链接实用

    1.文本链接: <a href="http://www.meng.com/" target="_blank">访问meng!</a> 2 ...

  4. OSI七层模型含义

    应用层:由用户自己规定,只要形成的消息能与表示层接口.这包括各机互访协议,分布式数据库协议等. 表示层:是在满足用户需求的基础上,尽可能的节省传输费用而设置的.如文本压缩.常用词转换.加密.变更文件格 ...

  5. Android 修改TabLayout底部导航条Indicator的长短

    关于Tablayout,使用的应该很频繁了,但是底部导航条长短是固定死的,需要自己来改动长短,找了半天没找着方法,看了下官方建议,可以通过映射来修改自己想要的长短,其实也就几行代码的问题,看代码: p ...

  6. nginx的常用命令

    一.nginx的解压安装 #tar xzvf nginx-1.6.0.tar.gz #cd nginx-1.6.0 #./configure    --prefix=/home/weixin/loca ...

  7. 如何用C# 动态创建Access数据库和表?

    记得以前要动态的创建Access数据库的mdb文件都是采用DAO,用VC开发,一大堆的API,很是麻烦.而且以前工作中需要全新的access数据库,可以复制数据库,也可以把新的数据库放到资源里面,用新 ...

  8. 笨办法学Python(八)

    习题 8: 打印,打印 formatter = "%r %r %r %r" print formatter % (1, 2, 3, 4) print formatter % (&q ...

  9. 119. Pascal's Triangle II (Amazon) from leetcode

    Given a non-negative index k where k ≤ 33, return the kth index row of the Pascal's triangle. Note t ...

  10. 【转载】#336 - Declaring and Using a readonly Field

    You can make a field in a class read-only by using the readonly modifier when the field is declared. ...