一、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线程(二)代码部分的更多相关文章

  1. Python线程二

    转自:https://www.cnblogs.com/chengd/articles/7770898.html 1. threading.Lock() import threading balance ...

  2. python 进程和线程(代码知识部分)

    二.代码知识部分 一 multiprocessing模块介绍: python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情 ...

  3. Python实现C代码统计工具(二)

    目录 Python实现C代码统计工具(二) 声明 一. 问题提出 二. 代码实现 三. 效果验证 Python实现C代码统计工具(二) 标签: Python 代码统计 声明 本文将对<Pytho ...

  4. python基础(34):线程(二)

    1. python线程 1.1 全局解释器锁GIL Python代码的执行由Python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行.虽然 Py ...

  5. Python 多线程 使用线程 (二)

    Python中实现多线程需要使用到 threading 库,其中每一个 Thread类 的实例控制一个线程. Thread类 #类签名 def __init__(self, group=None, t ...

  6. [python] 线程简介

    参考:http://www.cnblogs.com/aylin/p/5601969.html 我是搬运工,特别感谢张岩林老师! python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件 ...

  7. python 常忘代码查询 和autohotkey补括号脚本和一些笔记和面试常见问题

    笔试一些注意点: --,23点43 今天做的京东笔试题目: 编程题目一定要先写变量取None的情况.今天就是因为没有写这个边界条件所以程序一直不对.以后要注意!!!!!!!!!!!!!!!!!!!!! ...

  8. python线程同步原语--源码阅读

    前面两篇文章,写了python线程同步原语的基本应用.下面这篇文章主要是通过阅读源码来了解这几个类的内部原理和是怎么协同一起工作来实现python多线程的. 相关文章链接:python同步原语--线程 ...

  9. Python线程和协程-day10

    写在前面 上课第10天,打卡: 感谢Egon老师细致入微的讲解,的确有学到东西! 一.线程 1.关于线程的补充 线程:就是一条流水线的执行过程,一条流水线必须属于一个车间: 那这个车间的运行过程就是一 ...

  10. Python 线程和进程

    一.什么是线程 1.线程是操作系统能够进行运算调度的最小单位.它被包含在进程中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 ...

随机推荐

  1. PAT1125:Chain the Ropes

    1125. Chain the Ropes (25) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Given ...

  2. Unity文档阅读 第一章 入门

    Before you learn about dependency injection and Unity, you need to understand why you should use the ...

  3. Codeforces Round #483 (Div. 2)

    题目链接: https://cn.vjudge.net/contest/229761 A题: n个数字,两个人轮流去数字,直到剩下最后一个数字为止,第一个人希望剩下的数字最小,第二个人希望数字最大,最 ...

  4. 【php增删改查实例】第十节 - 部门管理模块(新增功能)

    正常情况下,在一个部门管理页面,不仅仅需要展示列表数据,还需要基本的增删改操作,所以,我们先把之前写好的新增功能集成进来. 在toolbar中,添加一个新增按钮. <div id="t ...

  5. Tair总述

    Tair 是淘宝自己开发的一个分布式 key/value 存储引擎.tair 分为持久化和非持久化两种使用方式.非持久化的 tair 可以看成是一个分布式缓存.持久化的 tair 将数据存放于磁盘中. ...

  6. 找jar包的网站 还没用过2017.12.19

    http://www.findjar.com/index.x http://mvnrepository.com/这个比较好用

  7. 在腾讯云(windows)上搭建node.js服务器

    1:安装Node.js 使用MSI文件,并按照提示安装node.js,默认情况下,安装程序将 Node.js 发行到 C:\Program Files\nodejs. 但这里我们需要修改安装路径到:D ...

  8. MySQL中group_concat函数深入理解

    本文通过实例介绍了MySQL中的group_concat函数的使用方法,比如select group_concat(name) . 一.MySQL中group_concat函数 完整的语法如下: gr ...

  9. Java基础之抽象类

    /* 1.抽象类的概述: 动物不应该定义为具体的东西,而且动物中的吃,睡等也不应该是具体的. 我们把一个不是具体的功能称为抽象的功能,而一个类中如果有抽象的功能,该类必须是抽象类. 抽象类的特点: A ...

  10. MongoDB与python交互

    1.Pymongo PyMongo是Mongodb的Python接口开发包,是使用python和Mongodb的推荐方式.官方文档 2.安装 进入虚拟环境 sudo pip install pymon ...