1.多任务
多任务指的是在同一时间不同任务需要同时进行的场景,比如边听歌边刷题,边看电视边吃饭…
import time
# 吃饭
def Eat():
for i in range(4):
print('eating...')
time.sleep(1)
# 看电视
def Watch():
for i in range(4):
print('watching...')
time.sleep(1)
if __name__ == '__main__':
Eat()
Watch()
# --结果:--
# eating...
# eating...
# eating...
# eating...
# watching...
# watching...
# watching...
# watching...
嗯,,,但这是同时进行吗?仔细一想他们是有先后顺序的吧。
2.主线程与子线程
2.1 何谓线程、主线程及子线程
- 线程:每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。
- 主线程:程序启动时自己执行本身的代码的线程
子线程:用户自己创建的线程
- 主线程和子线程之间的关系:主线程是在子线程结束之后再执行的
- 实现方式:join()方法,使子线程结束后再执行主线程;setDaemon()守护线程,不会等待子线程,有空就执行
看看代码和结果消化一下吧!
import threading
import time
def speak():
# 子线程
print('A先说!')
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=speak)
t.start()
print('B有话要说!')
结果(执行了几十次吧)出现了以下三种情况:
在t.start()后面添加
t.join()
时间会按照先后顺序来了,但是结果确实只有第三种了,剩下的setDaemon()加了与没加效果一样
2.2 查看线程数量
有事我们需要查看有哪些进程运行了可以使用:threading.enumerate()
例如:
import threading
import time
def speak():
# 子线程
print('A先说!')
time.sleep(1)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=speak)
t.setDaemon(True)
t.start()
print('B有话要说!')
print(threading.enumerate())
print('共有'+str(len(threading.enumerate()))+'个线程...')
# ---结果:---
# A先说!
# # A先说!
# # A先说!
# # A先说!
# # A先说!B有话要说!
# # [<_MainThread(MainThread, started 10900)>, <Thread(Thread-1, started daemon 5192)>, <Thread(Thread-2, started daemon 11456)>, <Thread(Thread-3, started daemon 12488)>, <Thread(Thread-4, started daemon 10588)>, <Thread(Thread-5, started daemon 4780)>]
# #
# # 共有6个线程...
2.3 创建子线程
当然,为了简化子线程的创建,我们通过类的继承来实现,而我们所需要实现的内容,都在run方法之中进行重写。
import threading
import time
class MyThread(threading.Thread):
# 继承threading.Thread类创建子线程
def __init__(self,name):
super().__init__(name=name)
def run(self):
for i in range(3):
print('我是{}{}'.format(self.name,i))
if __name__ == '__main__':
thread = MyThread('子线程')
thread.start()
# --结果:--
# 我是子线程0
# 我是子线程1
# 我是子线程2
2.4 线程间的通信
- 在⼀个函数中,对全局变量进⾏修改的时候,是否要加global要看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使⽤global,仅仅是修改了指 向的空间中的数据,此时不⽤必须使⽤global
- 线程是共享全局变量
- 多线程参数的使用:
threading.Thread(target=test, args=(num,))
3.线程间的资源竞争
- 一个线程写入以及读取不会对资源的分配产生影响,但是当多个线程进行写入和读取时,他们之间就会产生资源竞争
- 具体的资源竞争我们可以通过一个实例来进进行查看
import threading
import time
num = 0
def Thread1(nums):
global num
for i in range(nums):
num += 1
print('Thread1---{}'.format(num))
def Thread2(nums):
global num
for i in range(nums):
num += 1
print('Thread1---{}'.format(num))
if __name__ == '__main__':
t1 = threading.Thread(target=Thread1, args=(1000000,))
t2 = threading.Thread(target=Thread2, args=(1000000,))
t1.start()
t2.start()
time.sleep(2)
print('main---{}'.format(num))
# --结果:--
# Thread1---1074807
# Thread1---1400032
# main---1400032
以上结果显示,输出的结果不为我们所认为正确的答案,其原因就是资源竞争,那么何谓资源竞争?
- 当系统中供多个进程所共享的资源,不足以同时满足它们的需要时,引起它们对资源的竞争而产生死锁。
4.互斥锁与死锁
4.1 互斥锁
- 当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,直到该线程释放资源,将资源的状态变成"⾮锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性
import threading
# 创建锁
mutex = threading.Lock()
# 锁定锁
mutex.acquire()
# 解锁
mutex.release()
4.2 死锁
- 在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源,就会造成死锁
上个代码仔细再体会一下:
import threading
import time
# 创建锁A、B
mutexA = threading.Lock()
mutexB = threading.Lock()
class thread1(threading.Thread):
def run(self):
mutexA.acquire()
print('thread1--A上锁成功')
time.sleep(1)
mutexB.acquire()
print('thread1--B上锁成功')
mutexA.release()
mutexB.release()
class thread2(threading.Thread):
def run(self):
mutexB.acquire()
print('thread2--B上锁成功')
time.sleep(1)
mutexA.acquire()
print('thread2--A上锁成功')
mutexB.release()
mutexA.release()
if __name__ == '__main__':
T1 = thread1()
T2 = thread2()
T1.start()
T2.start()
# --结果:--
# thread1--A上锁成功
# thread2--B上锁成功
4.3 避免死锁
5.Queue线程
相信大家对于数据结构中的栈和队列都不陌生吧,栈在这里我们不做介绍,我们就看看队列Queue
- 在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模 块。Python中的queue模块中提供了同步的、线程安全的队列类,包括 FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。其相关方法如下:
方法 |
功能 |
empty() |
判断队列是否为空 |
full() |
判断队列是否满 |
put() |
向队列中放入元素 |
get() |
从队列中取出元素 |
qsize() |
获取队列长度 |
from queue import Queue
q = Queue(3)
# 判断队列是否为空
print(q.empty()) #True
# 判断队列是否满了
print(q.full()) #False
# 放入元素
q.put(111)
q.put(444)
q.put(666)
# 设置timeout或put_nowait当队列满了直接跳出
# q.put(886,timeout=2)
# 获取队列长度
print(q.qsize()) #3
# 获取元素
print(q.get()) #111
print(q.get()) #444
print(q.get()) #666
6.线程同步的实现
例如实现以下会话:
老师:小明?
小明:到。
老师:给我讲讲同步线程的实现吧!
小明:我…试试吧!
代码如下:
import threading
class XiaoMing(threading.Thread):
def __init__(self,cond):
super().__init__(name='小明')
self.cond = cond
def run(self):
self.cond.acquire()
self.cond.wait()
print('{}:在。'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}:我...试试吧!'.format(self.name))
self.cond.notify()
self.cond.release()
class Teacher(threading.Thread):
def __init__(self,cond):
super().__init__(name='老师')
self.cond = cond
def run(self):
self.cond.acquire()
# self.cond.wait()
print('{}:小明?'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}:给我讲讲同步线程的实现吧!'.format(self.name))
self.cond.notify()
self.cond.release()
if __name__ == '__main__':
cond = threading.Condition()
teacher = Teacher(cond)
xiaoming = XiaoMing(cond)
xiaoming.start()
teacher.start()
ps:一定要把先进行的对话放在后面启动。否则会导致堵塞。