python线程(二)代码部分
一、threading类简介
1、threading.Thread类参数简介
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
group:目前此参数为None,在实现ThreadGroup类时为将来的扩展保留。
target:target接收的是一个函数的地址,由run()方法调用执行函数中的内容。默认为无,表示未调用任何内容。
name :线程名,可自行定义。
args:target接收的是函数名,此函数的位置参数以元组的形式存放在args中,用于执行函数时调用。
kwargs :target接收的是函数名,此函数的关键字参数以字典的形式存放在kwargs中,用于执行函数时调用。
daemon:如果为True表示该线程为守护线程。
2、threading.Thread类的常用方法
start():开启线程,一个Thread对象只能调用一次start()方法,如果在同一线程对象上多次调用此方法,则会引发RuntimeError。
run():执行start()方法会调用run(),该方将创建Thread对象时传递给target的函数名,和传递给args、kwargs的参数组合成一个完整的函数,并执行该函数。run()方法一般在自定义Thead类时会用到。
join(timeout=None):join会阻塞、等待线程,timeout单位为秒,因为join()总是返回none,所以在设置timeout调用join(timeout)之后,需要使用isalive()判断线程是否执行完成,如果isalive为True表示线程在规定时间内没有执行完,线程超时。如果join(timeout=None)则会等待线程执行完毕后才会执行join()后面的代码,一般用于等待线程结束。
name:获取线程名。
getName():获取线程名。
setName(name):设置线程名。
ident:“线程标识符”,如果线程尚未启动,则为None。如果线程启动是一个非零整数。
is_alive():判断线程的存活状态,在run()方法开始之前,直到run()方法终止之后。如果线程存活返回True,否则返回False。
daemon:如果thread.daemon=True表示该线程为守护线程,必须在调用Start()之前设置此项,否则将引发RuntimeError。默认为False
isDaemon():判断一个线程是否是守护线程。
setDaemon(daemonic):设置线程为守护线程。
二、threading.Thread的简单使用
创建线程:
from threading import Thread def work(args,kwargs=None):
print(args)
print(kwargs) if __name__ == "__main__":
t = Thread(target=work, args=(("我是位置参数"),), kwargs={'kwargs': '我是关键字参数'}, name='我是线程demo')
print(t.name) # 打印线程名
t.start() # 开启线程
print('我是主线程') # 打印内容如下
我是线程demo
我是位置参数
我是关键字参数
我是主线程
由上面的打印内容我们可以看出,在执行完所有线程后才执行的主线程print。如果是多进程的话会先执行主进程中的print然后才会执行子进程的print。主要是因为开启进程相比于开启线程更加耗费时间。
自定义线程类
from threading import Thread def work(args,kwargs=None):
print(args)
print(kwargs)
class MyThread(Thread): # 使用继承Thread的方式,自定义线程类
def __init__(self,target=None, name=None,
args=(), kwargs=None, *, daemon=None):
# 如果要给对象封装属性,必须先调用父类
super().__init__()
if kwargs is None:
kwargs = {}
self._target = target
self._name = name
self._args = args
self._kwargs = kwargs
def run(self): # 必须要有run类,因为start要调用
print(f"我重写了Thread类的run")
self._target(*self._args,**self._kwargs) if __name__ == "__main__": t = MyThread(target=work,args=(('我是位置参数'),),kwargs={'kwargs':'我是关键字参数'},name='我是自定义线程类') # 创建线程对象
print(t.name) # 打印线程名
t.start() # 开启线程
print("主线程") # 打印内容如下
我是自定义线程类
我重写了Thread类的run
主线程
我是位置参数
我是关键字参数
多进程和多线程的效率对比
from threading import Thread
from multiprocessing import Process
import time def thread_work(name):
print(f"{name}")
def process_work(name):
print(f"{name}") if __name__ == "__main__":
# 进程执行效率
pro = []
start = time.time()
for i in range(3):
p = Process(target=process_work,args=(("进程-"+str(i)),))
p.start()
pro.append(p)
for i in pro:
i.join()
end = time.time()
print("进程运行了:%s" %(end - start))
# 线程执行效率
thread_l = []
start = time.time()
for i in range(3):
t = Thread(target=process_work, args=(("线程-" + str(i)),))
t.start()
thread_l.append(t)
for i in thread_l:
i.join()
end = time.time()
print("进程运行了:%s" % (end - start)) # 打印内容如下
进程-0
进程-1
进程-2
进程运行了:0.18501067161560059
线程-0
线程-1
线程-2
进程运行了:0.004000186920166016
我们可以从时间上看出,线程的效率是远远高于进程的。
守护线程
主线程会等待所有非守护线程执行完毕后,才结束主线程。主进程是进程内的代码结束后就结束主进程。
对比守护进程,代码执行完毕后立即关闭守护进程,因为在主进程看来代码执行完毕,主进程结束了,所以守护进程在代码结束后就被结束了。
守护线程会等待主线程的结束而结束。这是因为如果主线程结束意味着程序结束,主线程会一直等着所有非守护线程结束,回收资源然后退出程序,所以当所有非守护线程结束后,守护线程结束,然后主线程回收资源,结束程序。
下面对比守护进程和守护线程的示例:
先来看守护进程:
from multiprocessing import Process def process_work(name):
print(f"{name}") if __name__ == "__main__":
p = Process(target=process_work,args=("守护进程"))
p.daemon=True
p.start()
print("主进程") # 打印内容如下
主进程
只打印了主进程,也就是说守护进程还没来得及执行程序就结束了。
再来看守护线程:
from threading import Thread def thread_work(name):
print(name) if __name__ == "__main__":
t = Thread(target=thread_work,args=("守护线程",))
t.daemo=True
t.start()
print("\n主线程") # 打印内容如下
守护线程
主线程
也许你会说是由于线程太快了,所以才执行了守护线程。下面我们在线程中阻塞一段时间,在来看看会发生什么效果。
from threading import Thread
import time
def thread_work(name):
time.sleep(3) # 阻塞3秒
print(name) if __name__ == "__main__":
t = Thread(target=thread_work,args=("守护线程",))
t.daemo=True
t.start()
print("\n主线程") # 打印内容如下
主线程
守护线程
守护线程还是被执行了,如果是守护进程,守护进程里的代码是不会被执行的。
线程锁Lock
Lock也称线程同步锁,互斥锁,原子锁,该对象只有两个方法:
acquire(blocking=True, timeout=-1):加锁。
参数:
blocking:当为True时表示加锁,只允许一个线程执行被加锁的代码。直到遇到release()解锁后其它线程才可以执行加锁部分的代码。当为False时表示不加锁,并且不能调用release()否则会报RuntimeError。
timeout:设置加锁时间,单位为秒。-1表示一直等待线程release()后,才允许其它线程执行加锁的代码。
release():释放锁。
未加锁的代码示例
from threading import Thread
import time def work():
global n
temp = n
time.sleep(0.1) # 由于线程太快了,所以这里停顿下
n = temp -1 if __name__ == "__main__":
n = 100
t_l = []
for i in range(100):
t = Thread(target=work,args=())
t.start()
t_l.append(t)
for i in t_l:
i.join()
print(n) # 打印内容如下
99
在我们看来其实值应该是0的但却是99,就因为短暂停了0.1秒导致结果发生了变化。而我们这0.1秒的停留是模拟网络延迟或者进程调度等原因。造成了数据的结果的错乱。这个时候我们就需要线程锁来保证数据的安全性。
下面我们就通过给程序加锁,来保证数据的安全性:
from threading import Thread,Lock
import time def work(lock):
lock.acquire() # 加锁
global n
temp = n
time.sleep(0.1) # 由于线程太快了,所以这里停顿下
n = temp -1
lock.release() # 解锁 if __name__ == "__main__":
n = 100
t_l = []
lock = Lock() # 得到一把锁对象
for i in range(100):
t = Thread(target=work,args=(lock,))
t.start()
t_l.append(t)
for i in t_l:
i.join()
print(n) # 打印内容如下
0
我们会发现程序和上一个示例的运行效率上有着很大的差别。明显加锁后程序的运行效率降低了,我们管这种锁叫做线程同步锁,使原本并行的程序编程了串行所以程序的效率会慢很多,但是程序的运行结果是正确的。在程序的运行效率和数据的正确性,我们应首先确保程序的正确性然后在考虑程序的效率。
递归锁RLock
死锁与递归锁:
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,出现了一种互相等待的情况,它们都将无法进行下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下模拟产生死锁。
from threading import Lock,Thread def work():
# 获取锁
lock.acquire()
print("我是工作线程 1")
# 释放锁
lock.release() def work_2():
# 获取锁
lock.acquire()
print("我是工作线程 2")
work() # 调用工作线程1,造成死锁
# 释放锁
lock.release() if __name__ == '__main__':
# 生成lock实例
lock = Lock()
# 开始3个线程
for i in range(3):
t = Thread(target=work_2)
t.start()
print('我是主线程') # 打印内容如下
我是主线程
我是工作线程 2 # 此时程序处于死锁状态
我们可以理解为一把锁对象只能创建一把锁,这把锁必须release后,才能再次使用。否则程序就会被锁住,等待解锁。如上面的代码,work_2执行创建一个lock锁,调用work又遇到一把lock锁,而在work_2中的lock锁没有被解锁,所以程序在work中等待lock解锁,最终造成了程序出现了死锁,下面我们使用递归锁RLock来解决上面的问题。
关于RLock的用法及方法一样,所以这里就不再重复了。
使用RLock避免死锁:
from threading import RLock,Thread def work():
# 获取锁
lock.acquire()
print("我是工作线程 1")
# 释放锁
lock.release() def work_2():
# 获取锁
lock.acquire()
print("我是工作线程 2")
work() # 调用工作线程1,造成死锁
# 释放锁
lock.release() if __name__ == '__main__':
# 使用递归锁RLock
lock = RLock()
# 开始3个线程
for i in range(3):
t = Thread(target=work_2)
t.start()
print('我是主线程') # 打印内容如下
我是工作线程 2
我是工作线程 1
我是工作线程 2
我是工作线程 1
我是工作线程 2
我是主线程
我是工作线程 1
使用递归锁后程序运行正常了。
条件对象Condition(lock=None)
Condition条件变量,与锁相关联,在实例化对象时可以给其传入一把锁,如果不传,会默认创建一把递归锁。目前我对它的就理解是它是一把带通知,挂起线程功能的锁。它可以挂起线程,然后发送通知激活线程,并且还可以加锁,属于一把高级锁,下面我们来看看常用的方法。
1、Condition类的方法
class threading.Condition([lock])
acquire():加锁、与Lock、RLock中的用法一致,这里不过多解释。
release():解锁、与Lock、RLock中的用法一致,这里不过多解释。
wait(timeout=None):挂起线程,如果timeout是None则必须等到notify或notify_all
后线程才会被激活,并且被激活的线程会重新获取到一把锁,线程被激活后从wait挂起的位置继续向下执行。如果指定timeout超时时间,单位为秒,表示线程挂起一段时间后在继续执行。注意:如果调用线程在调用此方法时未获取锁,则会引发RuntimeError。
wait_for(predicate, timeout=None):这个不知道该如何解释。
notify(n=1):激活被挂起的线程,n表示激活n个被挂起的线程,注意:如果调用线程在调用此方法时未获取锁,则会引发RuntimeError。
notify_all():激活所有被挂起的线程,注意:如果调用线程在调用此方法时未获取锁,则会引发RuntimeError。。
2、下面是一个无聊的实例:
from threading import Condition,Thread
import time def consume():
'''消费者'''
global cv
global num
cv.acquire()
while True:
num -= 1
if num <= 0:
print('-' * 20)
cv.notify() # 激活生产者线程
cv.wait()
print(f'消费数据-{num}')
time.sleep(2)
cv.release() def produce():
'''生产者'''
global cv
global num
cv.acquire()
while 1:
num += 1
print(f'生产数据-{num}')
if num >= 5:
print('-' * 20)
cv.notify() # 激活消费者线程
cv.wait()
time.sleep(0.5)
cv.release() if __name__ == '__main__':
cv = Condition() # 实例化条件对象
num = 0
# 开启线程
produce_t = Thread(target=produce, args=())
consume_t = Thread(target=consume, args=())
produce_t.start()
consume_t.start()
Threading类的方法:
threading.
active_count
():获取当前活动的线程对象数量。
threading.
current_thread
():获取当前的线程对象。如果调用方的控制线程不是通过线程模块创建的,则返回功能有限的虚拟线程对象。
threading.
get_ident
():获取线程标识符。
threading.
enumerate
():这个比较厉害,可以获取当前活动的所有线程对象的列表。该列表包括后台线程和使用current_thread()创建的虚拟线程。以列表的形式返回。
threading.
main_thread
():获取主线程对象。
Threading模块就简单介绍到这里吧,参考文档:https://docs.python.org/3/library/threading.html#threading.Condition.notify
下一篇:python协程简介:https://www.cnblogs.com/caesar-id/p/10771369.html
python线程(二)代码部分的更多相关文章
- Python线程二
转自:https://www.cnblogs.com/chengd/articles/7770898.html 1. threading.Lock() import threading balance ...
- python 进程和线程(代码知识部分)
二.代码知识部分 一 multiprocessing模块介绍: python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情 ...
- Python实现C代码统计工具(二)
目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...
- python基础(34):线程(二)
1. python线程 1.1 全局解释器锁GIL Python代码的执行由Python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行.虽然 Py ...
- Python 多线程 使用线程 (二)
Python中实现多线程需要使用到 threading 库,其中每一个 Thread类 的实例控制一个线程. Thread类 #类签名 def __init__(self, group=None, t ...
- [python] 线程简介
参考:http://www.cnblogs.com/aylin/p/5601969.html 我是搬运工,特别感谢张岩林老师! python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件 ...
- python 常忘代码查询 和autohotkey补括号脚本和一些笔记和面试常见问题
笔试一些注意点: --,23点43 今天做的京东笔试题目: 编程题目一定要先写变量取None的情况.今天就是因为没有写这个边界条件所以程序一直不对.以后要注意!!!!!!!!!!!!!!!!!!!!! ...
- python线程同步原语--源码阅读
前面两篇文章,写了python线程同步原语的基本应用.下面这篇文章主要是通过阅读源码来了解这几个类的内部原理和是怎么协同一起工作来实现python多线程的. 相关文章链接:python同步原语--线程 ...
- Python线程和协程-day10
写在前面 上课第10天,打卡: 感谢Egon老师细致入微的讲解,的确有学到东西! 一.线程 1.关于线程的补充 线程:就是一条流水线的执行过程,一条流水线必须属于一个车间: 那这个车间的运行过程就是一 ...
- Python 线程和进程
一.什么是线程 1.线程是操作系统能够进行运算调度的最小单位.它被包含在进程中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 ...
随机推荐
- File文件的读写操作RandomAccessFile类
1.Java提供了一个对文件随机访问的操作,访问包括读和写操作,该类名是RandomAccessFile,该类的读写是基于指针的操作. 2.RandomAccessFile在堆文件进行随机访问操作时有 ...
- mac下的readelf和objdump
ELF文件包括: (1)可重定位的目标文件 (2)可执行的目标文件 (3)可被共享的目标文件 可以用file命令来看目标文件是否是ELF文件 在linux下,用readelf来看ELF头部或者其它各s ...
- Oracle-02:SQL语言的分类或者说SQL语言的组成
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 小结一版: 01.DDL(Data Definition Language)数据定义语言. 用来创建数据库中 ...
- 深入解读Resnet
残差网络的设计目的 随着网络深度增加,会出现一种退化问题,也就是当网络变得越来越深的时候,训练的准确率会趋于平缓,但是训练误差会变大,这明显不是过拟合造成的,因为过拟合是指网络的训练误差会不断变小,但 ...
- sql server 阻塞查询
在生产环境下,有时公司客服反映网页半天打不到,除了在浏览器按F12的Network响应来排查,确定web服务器无故障后.就需要检查数据库是否有出现阻塞 当时数据库的生产环境中主表数据量超过2000w, ...
- 深入js隐式类型转换
前言 相信刚开始了解js的时候,都会遇到 2 =='2',但是 1+'2' == '1'+'2'为false的情况,这时候应该会是一脸懵逼的状态,不得不感慨js弱类型的灵活让人发指,隐式类型转换就是这 ...
- kv.go
package clientv3 import ( pb "github.com/coreos/etcd/etcdserver/etcdserverpb" //protob ...
- Hibernate Annotation 生成数据库表(UUId)
User.java实体类 package com.tao.pojo; import javax.persistence.Column; //用注解的方式生成表 import javax.persist ...
- css:id选择器的权重>class选择器的权重=属性选择器的权重>元素选择器
最近的项目要自己写前端了,重新学习下前端的一下基本知识. 一般在css样式表中,上面的会被下面的覆盖,如下图,文字会显示蓝色: 所以按照正常的来说,下面的css样式,测试的文字应该还是蓝色 但结果,测 ...
- EffictiveC++笔记 第2章
Chapter 2 构造 / 析构 / 赋值 条款 05:了解C++ 默默编写并调用哪些函数 如果你写下: class Empty{ }; 事实上编译器会帮你补全: class Empty{ publ ...