继续讲多任务,上次咱们讲到了模拟多任务。这次接着讲创建线程。
一、多任务线程拓展二、线程同步的简单实现
一、多任务线程拓展
1.创建多任务线程的另一种方法
根据上一章我们知道,当调⽤Thread的时候,不会创建线程。 当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。
我们也可以通过继承Thread类创建线程。
先创建一个类,其中super()是动态拿到父类的初始化属性,继承于父类。再用run方法来实现。
import threading
import time
class A(threading.Thread):
def __init__(self,name):
super().__init__(name=name)
def run(self):
for i in range(5):
print(i)
if __name__ == "__main__":
t = A('test_name')
t.start()
2.线程间的通信(多线程共享全局变量)
在⼀个函数中,对全局变量进⾏修改的时候,要在变量前加上global。是否要加,看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使用global,此时是共享全局变量;仅仅是修改了指向的空间中的数据,此时**不是必须使用global。
创建两个子线程和一个主线程:
import threading
import time
num = 100
def demo1():
global num
num += 1
print('demo1-num%d'%num)
def demo2():
print('demo2-num%d' % num)
def main():
t1 = threading.Thread(target=demo1)
t2 = threading.Thread(target=demo2)
t1.start()
time.sleep(1)
t2.start()
print('main-num%d'%num)
if __name__ == '__main__':
main()
运行结果如下:
可以看到,第一个子线程的结果传递到了第二个子线程和主线程,也就是实现了线程间的通信。
另外,可以向线程中传递参数。
threading.Thread(target=test, args=(num,))
3.线程间的资源竞争及其解决方式
当多个线程同时需要相同的变量写入数据时 ,会产生资源竞争的问题。
就像下面这样:
import time
import threading
num = 0
def demo1(nums):
global num
for i in range(nums):
num += 1
print('demo1-num %d'%num)
def demo2(nums):
global num
for i in range(nums):
num += 1
print('demo2-num %d' % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('main-num %d'%num)
if __name__ == '__main__':
main()
我们想要实现每个线程用全局变量将数字相加100万次(想得到的结果是1000000、2000000、2000000),但却得到了下面这样的结果(出现了资源竞争问题):
我们应该怎么解决呢?这个时候就要使用一把锁把线程锁住。
3.1 锁的概念及使用
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 解锁
mutex.release()
咱们先来简单使用一下,在对涉及线程共享变量改变的部分上锁和解锁即可(这里仅对两个子线程修改即可):
mutex = threading.Lock() # 在前面先创建锁
def demo1(nums):
global num
mutex.acquire() # 上锁
for i in range(nums):
num += 1
mutex.release() # 解锁
print('demo1-num %d' % num)
def demo2(nums):
global num
mutex.acquire() # 上锁
for i in range(nums):
num += 1
mutex.release() # 解锁
print('demo2-num %d' % num)
这次达到了我们想要的结果:
以上这样创建的锁叫做不可重复的锁,只能创建一次。除此之外,还有可重复锁,可创建多次且不会出现死锁状态。
import time
import threading
num = 0
mutex = threading.RLock() # 可重复锁创建
def demo1(nums):
global num
# 加锁(可以看到这里上了两把锁,此时如果继续用lock就会出现死锁的状态)
mutex.acquire()
mutex.acquire()
for i in range(nums):
num += 1
# 解锁
mutex.release()
mutex.release()
print('demo1-num %d'%num)
def demo2(nums):
global num
mutex.acquire()
for i in range(nums):
num += 1
mutex.release()
print('demo2-num %d' % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('main-num %d'%num)
if __name__ == '__main__':
main()
二、线程同步的简单实现
比如我们想要实现这样的一个逻辑:
天猫精灵:小爱同学
小爱同学:在
天猫精灵:现在几点了?
小爱同学:你猜猜现在几点了
一问一答的形式,用下面的代码实现。
先导入thread模块
import threading
再创建两个类,用继承的方式来实现线程:
# 第一个类
class XiaoAi(threading.Thread):
def __init__(self,cond):
super().__init__(name='小爱')
def run(self):
print('{}: 在'.format(self.name))
print('{}: 你猜猜现在几点了'.format(self.name))
# 第二个类
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name='天猫')
def run(self):
print('{}: 小爱同学'.format(self.name))
print('{}: 现在几点了?'.format(self.name))
现在就要加上锁来控制线程的先后发生,这次用一个新的锁Condition
# 第一个类
class XiaoAi(threading.Thread):
def __init__(self,cond):
super().__init__(name='小爱')
self.cond = cond # 创建Condition锁
def run(self):
self.cond.acquire() # 此处上锁
print('{}: 在'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}: 你猜猜现在几点了'.format(self.name))
self.cond.release()
# 第二个类
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name='天猫')
self.cond = cond # 创建Condition锁
def run(self):
self.cond.acquire() # 此处上锁
print('{}: 小爱同学'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}: 现在几点了?'.format(self.name))
self.cond.release()
if __name__ == '__main__':
# mutex = threading.RLock()
cond = threading.Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
xiaoai.start()
tianmao.start()
其中通过线程间的唤醒、等待操作来实现这样的一个逻辑。
今天的多任务模块就先讨论到这里,下一章咱们接着唠。