什么是线程?
线程是进程内的独立的运行线路,是操作系统能够进行运算调度的最小单位,同时也是处理器调度的最小单位。线程被包含在进程之内,是进程中实际运作单位。
一个线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
Python中如何使用多线程?
首先要import threading
线程有两种调用方式,第一种是直接调用
import threading
import time
# 直接调用
def run(n):
print("task {0} {1}".format(n,threading.current_thread()))
time.sleep(2)
if __name__ == "__main__":
t1 = threading.Thread(target=run,args=("t1",)) # 参数括号内的逗号不能省略
t2 = threading.Thread(target=run,args=("t2",)) # 生成一个线程对象
t1.start() # 启动线程
t2.start()
# 现在同时启动50个线程
start = time.time()
for i in range(50):
t= threading.Thread(target=run,args=("t{0}".format(i),))
t.start()
end = time.time()
cost = end - start
print("cost time:",cost)
第二种是继承式
import threading
import time
'''继承式调用'''
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("running task{0} {1}".format(self.n,threading.current_thread()))
time.sleep(1)
if __name__ == "__main__":
start = time.time()
obj = []
for i in range(50):
t= MyThread("t{0}".format(i))
t.start()
end = time.time()
cost = end - start
print("cost time:",cost)
继承式
使用多线程对比函数调用,得出结论是:线程是并发执行的(同时执行),而函数调用只能是顺序执行,多线程执行大大提高了运行效率。
但是以上两个程序在运行的时候发现了一个共同的问题:拿继承式的代码来说,理论上50个线程执行完需要经过一秒多才会执行完毕,退出程序,但是实际情况却是开启50个线程之后立马就退出程序,执行时间不足0.01s。为什么呢?
答案是多线程。当前运行的线程是主线程,主线程启动了50个子线程,启动完毕后继续做自己的事情,每个线程之间互不干扰,并行运行。因此无法用此方式测定50个程序到底运行了多久。
主线程运行完毕即退出程序,如果不特殊处理,它不会等待子线程处理完毕。所以我们如果想要等待子线程的运行结果,需要加上join()语句。
import threading
import time
'''继承式调用'''
class MyThread(threading.Thread):
def __init__(self,n):
super(MyThread,self).__init__()
self.n = n
def run(self):
print("running task{0} {1}".format(self.n,threading.current_thread()))
time.sleep(1)
if __name__ == "__main__":
start = time.time()
obj = []
for i in range(50):
t= MyThread("t{0}".format(i))
t.start()
obj.append(t) # 为了不阻塞后面线程的启动,现将其加入列表
for j in obj:
j.join()
print(threading.current_thread()) # 证明主线程
end = time.time()
cost = end - start
print("cost time:",cost)
join等待
当线程被启动后,如果调用join()方法,则会在此线程执行完之前程序不往下走,也就是阻塞当前程序,使程序变为串行执行,这当然不是我们愿意看到的。在此程序中,我们希望它并发执行的同时,满足主线程等待所有子线程执行完毕后再结束这个条件。只需要在开启所有线程之后,一一地join()即可。
线程锁
在线程锁之间讲一下GIL全局性解释器锁。
首先要明白GIL并不是Python的特性,它是在实现Python解释器(CPython)时引入的一个概念。简单来说,GIL锁的存在使得:无论你开启多少个线程,无论你的CPU有多少核,在执行程序的时候同一时间只会占用一个核。所以,你以为的同时占用多核只是假象。再说一遍,这不是Python的特性,这只是CPyhton解释器的特性,其他类型的解释器如JPyhon,pypy等没有这个特性。之所以我们在使用Python多线程时没有感觉是单线程是因为上下文的切换。
在这里不详细多说,关于GIL全局性解释器锁的详细信息,有兴趣可以参考:https://www.cnblogs.com/cjaaron/p/9166538.html
我们最重要的是需要理解,Python多线程适合IO密集型操作,但在计算密集型操作中,多线程甚至没有单线程快。
再来讲互斥锁(也就是线程锁)
当多个线程同时修改一个数据的时候,可能会发生无法预估的错误,所以这时候要上锁。在这里叫做互斥锁。
def run():
lock.acquire() # 获取锁
global num
num += 1
lock.release() # 释放锁
time.sleep(1)
if __name__ == "__main__":
lock = threading.Lock() # 生成锁的实例
num = 0
obj = []
start = time.time()
for i in range(1000):
t= threading.Thread(target=run)
t.start()
obj.append(t)
for j in obj:
j.join()
end = time.time()
cost = end - start
print("cost time:",cost)
print("num:",num)
互斥锁
递归锁
互斥锁之间可以嵌套,但是没有理清逻辑容易造成死锁,无法解开。这里使用递归锁,实现多层上锁和多层解锁
def run1():
print("grab the first part data")
lock.acquire()
global num
num += 1
lock.release()
return num
def run2():
print("grab the second part data")
lock.acquire()
global num2
num2 += 1
lock.release()
return num2
def run3():
lock.acquire()
res = run1()
print('--------between run1 and run2-----')
res2 = run2()
lock.release()
print(res, res2)
if __name__ == '__main__':
num, num2 = 0, 0
lock = threading.RLock() # 递归锁,设锁和解锁必须是成对的
for i in range(10):
t = threading.Thread(target=run3)
t.start()
while threading.active_count() != 1:
print(threading.active_count())
else:
print('----all threads done---')
print(num, num2)
递归锁
需要注意的是:上锁和解锁必须是成对的。
信号量
信号量其实也是一种锁,用于限制线程的并发量,即同一时间只允许几个线程运行。
def run(n):
semaphore.acquire()
time.sleep(1)
print("run the thread: {0}\n".format(n))
semaphore.release()
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行
for i in range(20):
t = threading.Thread(target=run, args=(i+1,))
t.start()
while threading.active_count() != 1:
pass # print threading.active_count()
else:
print('----all threads done---')
信号量
Timer
创建一个有延迟的线程。
def hello():
print("hello, world")
t = Timer(30.0, hello)
t.start() # 30秒后执行此线程
Timer
Event
threading.Event()用来实现两个或多个线程之间的交互
有四个方法:
set() 设置标志位
clear()清空标志位
wait()如果已经设置标志位不会阻塞,如果没有设置标志位则产生阻塞
isSet() 判定是否设置了标志位。
接下来是一个车等红绿灯的例子,车是停还是行需要根据红绿灯来判定。将实例化若干个车(线程)和一个红绿灯(线程),来实现线程间交互
import threading
import time
import random
def light():
"红绿灯"
while True:
count = 1
event.set() # 标志位设定了,wait就不阻塞 #绿灯状态
while True:
if count == 1:
print('\033[42;1m--green light on---\033[0m')
time.sleep(10)
count = 2
elif count == 2:
print('\033[43;1m--yellow light on---\033[0m')
time.sleep(2)
count = 3
elif count == 3:
event.clear() # 标志位被清空,wait()阻塞
print('\033[41;1m--red light on---\033[0m')
time.sleep(10)
break
def car(n):
while 1:
time.sleep(random.randrange(8))
if event.isSet(): # 判断标志位是否设定
print("car [%s] is running.." % n)
else:
print("car [%s] is waiting for the red light.." %n)
if __name__ == '__main__':
event = threading.Event()
Light = threading.Thread(target=light)
Light.start()
for i in range(3):
t = threading.Thread(target=car,args=(i,))
t.start()
小车等红绿灯
第二个例子,员工过门。如果门是开的,员工直接过门;如果们是关的,员工需要刷卡开门再过门。
import threading
import time
import random
def door():
count = 0
while True:
if door_event.is_set():
print("\033[32;1mdoor opening....\033[0m")
count +=1
else:
print("\033[31;1mdoor closed...., swipe to open.\033[0m")
count = 0 #清空计时器
door_event.wait()
if count > 3:#门开了已经3s了,该关了
door_event.clear()
time.sleep(0.5)
def staff(n):
print("staff [%s] is comming..." % n )
while True:
if door_event.is_set():
print("\033[34;1mdoor is opened, passing.....\033[0m")
break
else:
print("staff [%s] sees door got closed, swipping the card....." % n)
door_event.set()
print("door is opening...")
time.sleep(0.5)
if __name__ == "__main__":
door_event = threading.Event() #设置事件
door_thread = threading.Thread(target=door) # 实例化“门”线程并启动
door_thread.start()
for i in range(5):
p = threading.Thread(target=staff,args=(i,))
time.sleep(random.randrange(3))
p.start()
员工过门
队列queue
线程间的数据通信。需要导入模块queue。
分为三种:
1. queue.Queue(maxsize=0) 先入先出
qsize() 查询队列中数据的个数
put(item,block=Ture,timeout=None) 往队列中放入数据
get(block=True,timeout=None) 取出数据。当队列中没有数据的时候,get将会造成阻塞,直到队列中又存入数据为止。参数block=False不会阻塞,timeout=1表示只卡1s
empty() 当队列为空返回True
full() 当队列已满返回True
def put():
#time.sleep(1) # 将执行此函数的线程滞后,更好地观摩多线程的运行效果
try:
for i in range(50):
cou = i
q.put(i + 1, block=False)
else:
print("存入了{0}个".format(cou+1))
except queue.Full:
print("队列已满,存入了{0}个".format(cou))
def get():
try:
for i in range(50):
print(q.get(block=False))
except queue.Empty:
print("队列已空,无法取出数据")
def qsize():
# time.sleep(1)
print("此时队列中个数:",q.qsize())
put_thr = threading.Thread(target=put)
get_thr = threading.Thread(target=get)
qsi_thr = threading.Thread(target=qsize)
q = queue.Queue(5) # 队列最多同时容纳5个数据
put_thr.start()
qsi_thr.start()
get_thr.start()
Queue
在代码中,有用到异常处理,是因为如果队列满了再存入数据会触发异常,队列空了再取数据也会触发异常。
2. queue.LifoQueue(maxsize=0) 先入后出
方法与上同
3. PriorityQueue() 存储数据时可设置优先级
import queue
import threading
'''
按照优先级取出数据。
'''
q = queue.PriorityQueue() # 生成队列
q.put((5,"lwz"))
q.put((10,"alex"))
q.put((2,"chenronghua")) # 数字越小优先级越高
print(q.get())
print(q.get())
print(q.get())
PriorityQueue
生产者消费者模型
该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。
为什么要使用生产者和消费者模式?
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。
在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。
同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,
而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,
而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
import queue
import threading
import time
def Producer():
count = 0
while True:
count += 1
q.put("骨头{0}".format(count))
print("生产了骨头{0}".format(count))
time.sleep(1)
def Customer(name):
while 1:
print("{0}取到并且吃了{1}".format(name,q.get()))
time.sleep(0.1)
if __name__ == "__main__":
q = queue.Queue()
c1 = threading.Thread(target=Customer,args=("alex",))
c2 = threading.Thread(target=Customer,args=("Chen",))
p = threading.Thread(target=Producer)
c1.start()
c2.start()
p.start()
生产者消费者模型
一个简单的python线程池框架
初学python,实现了一个简单的线程池框架,线程池中除Wokers(工作线程)外,还单独创建了一个日志线程,用于日志的输出.线程间采用Queue方式进行通信. 代码如下:(不足之处,还请高手指正) ...
对Python线程池
本文对Python线程池进行详细说明介绍,IDE选择及编码的解决方案进行了一番详细的描述,实为Python初学者必读的Python学习经验心得. AD: 干货来了,不要等!WOT2015 北京站演讲P ...
初学Python
初学Python 1.Python初识 life is short you need python--龟叔名言 Python是一种简洁优美语法接近自然语言的一种全栈开发语言,由"龟叔&quo ...
python——线程与多线程进阶
之前我们已经学会如何在代码块中创建新的线程去执行我们要同步执行的多个任务,但是线程的世界远不止如此.接下来,我们要介绍的是整个threading模块.threading基于Java的线程模型设计.锁( ...
python——线程与多线程基础
我们之前已经初步了解了进程.线程与协程的概念,现在就来看看python的线程.下面说的都是一个进程里的故事了,暂时忘记进程和协程,先来看一个进程中的线程和多线程.这篇博客将要讲一些单线程与多线程的基础 ...
[python] 线程简介
参考:http://www.cnblogs.com/aylin/p/5601969.html 我是搬运工,特别感谢张岩林老师! python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件 ...
无开发经验,初学python
1.无开发经验,初学python 如果你不会其他语言,python是你的第一门语言: A Byte of Python (简明python教程,这个有中文版简明 Python 教程)是非常好的入门 ...
PYTHON线程知识再研习A
前段时间看完LINUX的线程,同步,信息号之类的知识之后,再在理解PYTHON线程感觉又不一样了. 作一些测试吧. thread:模块提供了基本的线程和锁的支持 threading:提供了更高级别,功 ...
Python 线程(threading) 进程(multiprocessing)
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
随机推荐
Python中,添加写入数据到已经存在的Excel的xls文件,即打开excel文件,写入新数据
背景 Python中,想要打开已经存在的excel的xls文件,然后在最后新的一行的数据. 折腾过程 1.找到了参考资料: writing to existing workbook using xlw ...
border-radius讲解1
如今CSS3中的border-radius出现后,让我们没有那么多的烦恼了,首先制作圆角图片的时间是省了,而且其还有多个优点:其一减少网站的维护的工作量,少了对图片的更新制作,代码的替换等等;其二.提 ...
java基础知识汇总
1. 类的访问控制权限共有两种:public和默认.默认级别的类只能被同一个包中的类看到,包外不可见. 2. 类成员的访问权限共有四种:public,protected,private ...
【Netty】(4)—源码AbstractBootstrap
源码AbstractBootstrap 一.概念 AbstractBootstrap是一个工具类,用于服务器通道的一系列配置,绑定NioEventLoopGroup线程组,指定指定NIO的模式,指定子 ...
MyPython
目录 Python,那些不可不知的事儿 Python简介 Python环境搭建 从Hello World开始 Python中的数据类型 函数 模块 面向对象 More Python,那些不可不知的事儿 ...
熊猫ios手游直播教程 苹果投屏电脑
如今手游越来越火热,不管是大人小孩都喜欢在闲暇时刻玩一玩游戏,手机屏幕终归还是有点小的,所以有的小伙伴想要将手机投屏到电脑上,岂不是一件很好的事情,iPhone是有镜像投屏功能的,下面给大家分享熊猫i ...
160A
#include int main() { int n; int sum1=0, sum2=0, sum3=0; int x, y, z; scanf("%d ...
python 3.x 字典的11种方法
python 3.x 字典的11种方法2017年11月25日 01:02:11 Milton-Long 阅读数:535 标签: python python字典方法 更多个人分类: python-学习之 ...
VMWare Workstation使用总结几则[转]
VMWare Workstation使用总结几则 1.安装 使用GHOST盘安装时一定要注意,需要把空盘建立分区并设置为主分区 PQ的使用形式,进入PQ找到磁盘设置为启用 否则 启动后显示Boot ...
strops()