第十七天
Python多任务
其实,多任务就是操作系统可以同时运行多个任务。比如说我们可以一边写代码,一边听歌,一边上网,一边和别人聊天,这其实就是多任务的体现。
多任务的执行方式有两种:
1、并发:交替执行,是假的多任务,指的是任务数多于cpu核数,通过操作系统的各种任务调度算法,处理完一个任务处理下一个(时间轮片执行哪个执行哪个),然后实现用多个任务“一起”执行。其实不是一起执行的,只是处理的速度快,看似一起执行。
2、并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的,同时执行,是真的多任务,cpu一个一个单独执行。
1、线程
线程可以简单理解为同一进程中有多个计数器,每个线程的执行时间不确定,线程是操作系统调度执行的最小单位。
Python中使用线程,需要引入thread模块,但是这个模块在Python 2 中属于正常可用状态,但在 Python 3 中处于即将废弃的状态,虽然还可以用,但包名被改为 _thread,在Python3中一般使用threading模块。
1.1、单线程执行
import time
import threading
def dance():
print('小张在跳舞')
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=dance)
t.start() #启动线程执行任务调用start的时候start自动调用run方法
time.sleep(1) # 1s后执行下一次循环
1.2、多线程执行
import time
import threading
def tiao(): # 子线程
for i in range(5):
print('小李在跳舞')
time.sleep(1)
def sing(): # 子线程
for i in range(5):
print('小张在唱歌')
time.sleep(1)
def main(): # 主线程
t1 = threading.Thread(target=tiao) #target=函数--->指向函数
t2 = threading.Thread(target=sing) #target=函数--->指向函数
t1.start() #启动线程执行任务
t2.start() #启动线程执行任务
if __name__ == '__main__':
main()
注意:
1、调用thread,不会创建线程,只是创建一个对象;
2、当调用thread创建出来的实例对象的start方法的时候,才会创建线程以及让这个线程开始运行;
3、主线程会等待子线程结束之后才会结束,主线程一死,子线程也会死。线程的调度是随机的,并没有先后顺序。
1.3、枚举函数enumerate的用法
enumerate()是python的内置函数,在字典上是枚举、列举的意思。
用途:用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中,通过threading.enumerate()就可以获取线程列表。
import time
import threading
def tiao():
for i in range(5):
print('小李在跳舞')
time.sleep(1)
def sing():
for i in range(5):
print('小张在唱歌')
time.sleep(1)
def main():
t1 = threading.Thread(target=tiao)
t2 = threading.Thread(target=sing)
t1.start()
t2.start()
while True:
print(threading.enumerate())
if len(threading.enumerate())<=1:
break
time.sleep(1)
if __name__ == '__main__':
main()
2、多线程-共享全局变量
2.1、多线程-共享全局变量
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算
import threading
import time
g_num = 0
def test1():
global g_num
for i in range(100):
g_num += 1
print('test1方法,g_num的最后结果是%d'%(g_num))
def test2():
global g_num
for i in range(100):
g_num += 1
print('test2方法,g_num的最后结果是%d'%(g_num))
if __name__ == '__main__':
print('线程创建之前,g_num的值是%d'%(g_num))
t1 = threading.Thread(target=test1)
t1.start()
t2 = threading.Thread(target=test2)
t2.start()
print('两个线程创建之后,g_num的值是%d' %(g_num))
# test1方法,g_num的最后结果是125
# test2方法,g_num的最后结果是126
# test2方法,g_num的最后结果是127
# test2方法,g_num的最后结果是128
# test1方法,g_num的最后结果是129
# test1方法,g_num的最后结果是130
# test1方法,g_num的最后结果是131
我们观察到在test1中参杂这test2,也就说明线程是对全局变量随意修改可能造成多线程之间对全局变量的混乱、甚至数据的不正确(即线程非安全),也就是说如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确。我们可以在一个线程执行的时候time.sleep(1)来保证线程的正常执行(不一定),好处就是在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据。
2.2、多线程-共享全局变量-args参数
其实就是我们在定义线程target的时候,可以传递参数去子线程。
import threading
import time
g_nums = [1,2,3,4,5]
def test1(temp):
temp.append(44)
print('test1方法,temp的最后结果是%s'% str(temp))
def test2(temp):
print('test2方法,temp的最后结果是%s'% str(temp))
if __name__ == '__main__':
print('线程创建之前,g_nums的值是%s'% str(g_nums))
t1 = threading.Thread(target=test1,args=(g_nums,))
t1.start()
time.sleep(1)
t2 = threading.Thread(target=test2,args=(g_nums,))
t2.start()
print('两个线程创建之后,g_nums的值是%s' % str(g_nums))
3、线程同步
我们前面说了,多线程开发,难免会遇见线程间的资源争夺问题,其实线程间出现问题的原因归根到底就是线程1对某个数据操作的时候还没有结束的时候,另一个线程其实已经获得了时间轮片,就如同张三转账给李四,张三的money已经扣除,但是李四没有收到,在数据库中我们用事务来解决,那么在我们的python代码中,如何同步呢?就是加锁,加互斥锁。
3.1、互斥锁
多个线程对同一数据操作的时候,需要进行同步控制,保证多个线程安全访问竞争资源,这就是互斥锁同步机制。
互斥锁两个状态:锁定、非锁定。
也就是线程1对数据操作时,然后加锁,此时资源状态为锁定,其它线程无法对数据进行操作,除非线程1对数据操作完毕,将资源状态改为非锁定,其他线程才可以锁定该资源,对该资源操作。互斥锁保证每次只有一个线程对资源操作,从而保证多线程情况下数据的正确性。
import threading
import time
g_num = 0
# 创建一个互斥锁,默认没有上锁
mutex = threading.Lock()
def test1():
global g_num
mutex.acquire() #上锁
for i in range(100):
g_num += 1
print('test1方法,g_num的最后结果是%d'%(g_num))
mutex.release() #解锁
def test2():
global g_num
mutex.acquire() # 上锁
for i in range(100):
g_num += 1
print('test2方法,g_num的最后结果是%d'%(g_num))
mutex.release() # 解锁
if __name__ == '__main__':
print('线程创建之前,g_num的值是%d'%(g_num))
t1 = threading.Thread(target=test1)
t1.start()
t2 = threading.Thread(target=test2)
t2.start()
print('两个线程创建之后,g_num的值是%d' %(g_num))
注意:线程通过mutex.acquire()上锁的时候,那么之前没有上锁,那么此时上锁成功,上锁之前如果已经上锁,那么此时会堵塞,直到这个锁解开。
3.2、死锁(了解)
线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,那么就会形成死锁。
造成死锁的原因可以概括成三句话:
1.不同线程同时占用自己锁资源
2.这些线程都需要对方的锁资源
3.这些线程都不放弃自己拥有的资源
import threading
import time
mutexA = threading.Lock()
mutexB = threading.Lock()
class MyThreadA(threading.Thread):
def run(self):
mutexA.acquire() #对mutexA上锁
print('------MyThreadA---start------')
time.sleep(1) #等待一秒,此时mutexB上锁
mutexB.acquire() #这个时候堵塞 mutexB已经上锁过了
print('------MyThreadA---end------')
mutexB.release() #不释放锁 死锁
mutexA.release()
class MyThreadB(threading.Thread):
def run(self):
mutexB.acquire() #对mutexB上锁
print('------MyThreadB---start------')
time.sleep(1) #等待一秒,此时mutexA上锁
mutexA.acquire() #这个时候堵塞 mutexA已经上锁过了
print('------MyThreadB---end------')
mutexA.release() #不释放锁 死锁
mutexB.release()
if __name__ == '__main__':
t1 = MyThreadA()
t2 = MyThreadB()
t1.start()
t2.start()
网友评论