多任务
说起线程多多少少会和多任务挂钩,什么是多任务呢?多任务其实就是在同一时间同时做很多件事情
并发:CPU 小于当前执行的任务,是假的多任务并行:CPU 大于当前执行的任务,是真的多任务 多任务实现的几种方式:
线程进程协程
线程
线程,也被称为轻量进程,是处理器能够进行运算调度的最小单位,是进程内的一个执行单元,是进程的实际运作单位。多线程类似于执行多个同时执行多个不同的程序,有以下的优点:
使用线程可以把占据长时间的程序中的任务放到后台去处理程序运行速度加快在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
Python中线程的创建方式
函数式:
import threading
import time
"""
使用线程来完成边唱歌边跳舞
"""
def sing():
for i
in range(4):
print('I am a singer')
time
.sleep
(1)
def dance():
for i
in range(4):
print('I am a dancer')
time
.sleep
(1)
def main():
t1
= threading
.Thread
(target
=sing
)
t2
= threading
.Thread
(target
=dance
)
t1
.start
()
t2
.start
()
if __name__
== '__main__':
main
()
通过继承Threading.Thread类来创建线程
import threading
class A(threading
.Thread
):
pass
class B(threading
.Thread
):
pass
a
= A
()
b
= B
()
a
.start
()
b
.start
()
线程的具体分析
线程类Thread参数说明
group:线程组,目前只能使用Nonetarget:执行的目标任务名args:以元组的方式给执行任务传参kwargs:以字典的方式给执行任务传参name:线程名,一般不用设置
子线程与主线程的执行
setDaemon:用来保护主线程的
import threading
import time
def demo1():
for i
in range(4):
print('--demo1--')
time
.sleep
(1)
def main():
t
= threading
.Thread
(target
=demo1
)
t
.setDaemon
(True)
t
.start
()
print('111')
if __name__
== '__main__':
main
()
join()
import threading
import time
def demo1():
for i
in range(4):
print('--demo1--')
time
.sleep
(1)
def main():
t
= threading
.Thread
(target
=demo1
)
t
.start
()
t
.join
()
print('111')
if __name__
== '__main__':
main
()
综上情况: setDaemon(True)是用来保护主线程的,主线程不会去等待子线程结束。 join():是用来阻塞线程的,主线程会等待子线程的结束,再去结束主线程
线程的启动与线程的数量
使用threading.enumerate()判断线程的数量从而确定线程启动的位置
import threading
import time
def demo1():
for i
in range(3):
print('--demo1--')
time
.sleep
(1)
def demo2():
for i
in range(4):
print('--demo2--')
time
.sleep
(1)
def main():
print(threading
.enumerate())
t1
= threading
.Thread
(target
=demo1
, name
='demo1')
t2
= threading
.Thread
(target
=demo2
, name
='demo2')
print(threading
.enumerate())
t1
.start
()
t2
.start
()
print(threading
.enumerate()
if __name__
== '__main__':
main
()
综上描述:线程是在 运行t.start()之后才开始启动的
线程间共享全局变量
import threading
import time
num
= 100
def demo1():
global num
for i
in range(1000000):
num
+= 1
print('demo1-- {}'.format(num
))
def demo2():
global num
for i
in range(1000000):
num
+= 1
print('demo2-- {}'.format(num
))
def main():
t1
= threading
.Thread
(target
=demo1
)
t2
= threading
.Thread
(target
=demo2
)
t1
.start
()
t2
.start
()
print('--main-- {}'.format(num
))
if __name__
== '__main__':
main
()
此处需要注意一个问题: 当我们循环的时候过大时候,会造成资源抢占混乱的情况。可以使用给线程上锁或者线程同步的方法 解决
线程之间传参
我们可以使用 args 和 kwargs 在线程间传递参数
import threading
import time
num
= 100
def demo1(m
):
global num
for i
in range(m
):
num
+= 1
print('--demo1-- {}'.format(num
))
def demo2(m
):
global num
for i
in range(m
):
num
+= 1
print('--demo2-- {}'.format(num
))
def main():
t1
= threading
.Thread
(target
=demo1
, args
=(100,))
t2
= threading
.Thread
(target
=demo2
, args
=(100,))
t1
.start
()
t2
.start
()
print('--main-- {}'.format(num
))
if __name__
== '__main__':
main
()
使用kwargs 传值:
num
= 100
def demo1(m
):
global num
for i
in range(m
):
num
+= 1
print('--demo1-- {}'.format(num
))
def demo2(m
):
global num
for i
in range(m
):
num
+= 1
print('--demo2-- {}'.format(num
))
def main():
t1
= threading
.Thread
(target
=demo1
, kwargs
={'m': 100})
t2
= threading
.Thread
(target
=demo2
, kwargs
={'m': 100})
t1
.start
()
t2
.start
()
print('--main-- {}'.format(num
))
if __name__
== '__main__':
main
()
互斥锁与死锁
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作,从而保证多线程的情况下数据的准确性使用互斥锁的目的:保证多个线程访问共享数据时不会出现资源竞争以及数据混乱上锁与解锁的过程:
当一个线程调用锁的 acquire() 方法获得锁时,锁就进入“Locked”状态每次只有一个线程获得锁。当另一个线程想要获得锁时,该线程进入“Locked”状态,俗称“阻塞”,直到拥有锁的线程调用锁的“release()”方法释放锁后,锁进入“unLocked”状态线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使该线程进入运行(running)状态
mutex
= threading
.Lock
()
mutex
.acquire
()
mutex
.release
()
注意:
抢到锁的线程先执行,没有抢到锁的线程需要等待,等锁用完后需要释放,然后其他等待的线程再去抢这个锁,哪个线程先抢到哪个线程先执行。具体哪个线程 抢到的锁不是由我们决定的,是由CPU的资源调度决定的线程同步只能保证多个先线程安全访问竞争资源,最简单的同步机制是引入互斥锁当我们要更改共享数据时,先将其锁定,此时资源的状态为“ 锁定 ”,其他线程不能更改;直到该线程释放资源,将资源的状态变为“ 非锁定 ”,其他的线程才能再次锁定改资源。互斥锁保证了每次只有一个线程进行功写入操作,从而保证了数据的准确性
使用互斥锁来解决之前的资源抢占导致数据混乱的问题:
import threading
import time
mutex
= threading
.Lock
()
num
= 100
def demo1(m
):
global num
mutex
.acquire
()
for i
in range(m
):
num
+= 1
mutex
.release
()
print('--demo1-- {}'.format(num
))
def demo2(m
):
global num
mutex
.acquire
()
for i
in range(m
):
num
+= 1
mutex
.release
()
print('--demo2-- {}'.format(num
))
def main():
t1
= threading
.Thread
(target
=demo1
, args
=(1000000,))
t2
= threading
.Thread
(target
=demo2
, kwargs
={'m': 1000000})
t1
.start
()
t2
.start
()
mutex
.acquire
()
print('--main-- {}'.format(num
))
mutex
.release
()
if __name__
== '__main__':
main
()
死锁:在线程间共享多个资源的时候,如果两个线程分别得到一部分资源并且同时等待对方释放的资源,这时就会形成死锁
import threading
import time
class MyThread1(threading
.Thread
):
def run(self
):
mutexA
.acquire
()
print(self
.name
+'----do1---up----')
time
.sleep
(1)
mutexB
.acquire
()
print(self
.name
+'----do1---down----')
mutexB
.release
()
mutexA
.release
()
class MyThread2(threading
.Thread
):
def run(self
):
mutexB
.acquire
()
print(self
.name
+'----do2---up----')
time
.sleep
(1)
mutexA
.acquire
()
print(self
.name
+'----do2---down----')
mutexA
.release
()
mutexB
.release
()
mutexA
= threading
.Lock
()
mutexB
= threading
.Lock
()
if __name__
== '__main__':
t1
= MyThread1
()
t2
= MyThread2
()
t1
.start
()
t2
.start
()
线程同步
如果多个线程共同对某个数据进行修改,则可能出现不可预料的后果,为了保证数据的正确性,对多个线程进行同步。 使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间
使用Condition方法实现线程同步:
要求 :
天猫精灵:小爱同学小爱:在天猫精灵:现在几点了小爱:你猜猜现在几点了
import threading
from threading
import Condition
class TianMao(threading
.Thread
):
def __init__(self
, cond
):
super().__init__
(name
='天猫精灵')
self
.cond
= cond
def run(self
):
self
.cond
.acquire
()
print('{}:小爱同学'.format(self
.name
))
self
.cond
.notify
()
self
.cond
.wait
()
print('{}:现在几点了'.format(self
.name
))
self
.cond
.notify
()
self
.cond
.release
()
class XiaoAi(threading
.Thread
):
def __init__(self
, cond
):
super().__init__
(name
='小爱')
self
.cond
= cond
def run(self
) -> None:
with self
.cond
:
self
.cond
.wait
()
print('{}:在'.format(self
.name
))
self
.cond
.notify
()
self
.cond
.wait
()
print('{}:你猜现在几点了?'.format(self
.name
))
self
.cond
.notify
()
if __name__
== '__main__':
cond
= threading
.Condition
()
xiaoai
= XiaoAi
(cond
)
tianmao
= TianMao
(cond
)
xiaoai
.start
()
tianmao
.start
()
import threading
from threading
import Condition
"""
天猫精灵:小爱同学
小爱:在
天猫精灵:现在几点了
小爱:你猜猜现在几点了
"""
class XiaoAi(threading
.Thread
):
def __init__(self
, cond
):
super().__init__
(name
='小爱同学')
self
.cond
= cond
def run(self
) -> None:
with self
.cond
:
self
.cond
.wait
()
print('{}:在'.format(self
.name
))
self
.cond
.notify
()
self
.cond
.wait
()
print('{}:你猜现在几点了'.format(self
.name
))
self
.cond
.notify
()
class TianMao(threading
.Thread
):
def __init__(self
, cond
):
super().__init__
(name
='天猫精灵')
self
.cond
= cond
def run(self
) -> None:
with self
.cond
:
print('{}:小爱同学'.format(self
.name
))
self
.cond
.notify
()
self
.cond
.wait
()
print('{}:现在几点了?'.format(self
.name
))
self
.cond
.notify
()
def main():
cond
= Condition
()
xiaoai
= XiaoAi
(cond
)
tianmao
= TianMao
(cond
)
xiaoai
.start
()
tianmao
.start
()
if __name__
== '__main__':
main
()
这里需要注意:Condition需要加锁和解锁,将唤醒和通知的方法 放到加锁和解锁之间。 或者使用上下文管理器代替Condition的上锁与解锁
线程同步队列queue
Python的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
queue模块的常用方法有:
queue.empty() 如果队列为空,返回True,反之Falsequeue.full() 如果队列满了,返回True,反之Falsequeue.get([block[, timeout]])获取队列,timeout等待时间queue.get_nowait() 相当Queue.get(False)queue.put(item) 写入队列,timeout等待时间queue.put_nowait(item) 相当Queue.put(item, False)queue.qsize() 返回队列的大小
from queue
import Queue
q
= Queue
(4)
q
.put
(1)
q
.put
(2)
q
.put
(3)
q
.put
(4)
print(q
.empty
())
print(q
.full
())
print(q
.qsize
())
print(q
.get
())
print(q
.get
())
print(q
.get
())
print(q
.get
())
"""
put_nowait()和get_nowait()方法一样。
put_nowait()向队列中存放数据,当队列已经满的时候,会直接抛出queue.Full异常,而put()则会一直等待
"""
"""
在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括 FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。
"""
总结
使用多线程可以完成多任务只有线程启动,线程才会加入到活动线程列表线程之间的执行时无序的主线程会等待所有子线程结束后执行,如果有需要可以使用setDaemon 保护主线程自定义线程不能指定target,因为自定义线程里面的任务都指定在 run 方法中执行启动线程调用 start 方法,不要直接调用 run 方法多线程共享全局变量,很方便在多个线程间共享数据多个线程同时对同一个全局变量操作,会有可能出现资源竞争导致的数据错误问题线程的同步可以解决资源竞争的问题,但是会使多任务变为单任务锁的好处:
确保某段代码只能由一个线程从头至尾地完整执行 锁的坏处:
多线程执行变成了包含锁的某段代码,实际上只能以单线程模式执行,大大降低了执行效率锁使用不好容易出现死锁的状况 使用互斥锁时要注意死锁问题,需要及时将锁释放死锁一旦发生将会导致程序一直处于阻塞状态