此Python多任务系列的笔记均来源于b站Python高级编程的视频资料
目录
多任务:操作系统可以同时运行多个任务
- 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,所以看上去是一起执行的一样)
- 并行:指的是任务数小于等于cpu核数核数,即任务真的是一起执行的
一、线程的简单操作
Python的thread模块是比较比较底层的模块,python的threading模块是对thread做的一些封装,更好的被使用
import time
import threading
def saySorry():
print("dear i am sorry !")
time.sleep(1)
def sayHappy():
print("i am happy !")
time.sleep(1)
for i in range (5):
t1 = threading.Thread(target=saySorry)
t2 = threading.Thread(target=sayHappy)
t1.start()
t2.start()
time2 = time.time()
#输出
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
dear i am sorry !
i am happy !
查看线程数量
import time
import threading
def saySorry():
for i in range(3):
print("dear i am sorry ---1")
time.sleep(1)
def sayHappy():
for i in range(3):
print("i am happy ---2")
time.sleep(1)
if __name__ == "__main__":
t1 = threading.Thread(target=saySorry)
t2 = threading.Thread(target=sayHappy)
t1.start()
t2.start()
print("-----------")
while True:
length = len(threading.enumerate())
print("当前的线程数是:",length)
if length <= 1:
break
time.sleep(0.5)
#输出
dear i am sorry ---1
i am happy ---2
-----------
当前的线程数是: 3
当前的线程数是: 3
当前的线程数是: 3
dear i am sorry ---1
i am happy ---2
当前的线程数是: 3
当前的线程数是: 3
i am happy ---2
dear i am sorry ---1
当前的线程数是: 3
当前的线程数是: 2
当前的线程数是: 1
线程执行代码的封装
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread
就可以了,然后重写run
方法
python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而自己创建实例后,通过Thread类的start方法,可以启动该线程交给系统调度,当该线程获得执行的机会的时候,就会D调用run方法执行线程。
import time
import threading
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I am " + self.name + "//" +str(i)
print(msg)
if __name__ == "__main__":
t = MyThread()
t.start()
#输出
I am Thread-1//0
I am Thread-1//1
I am Thread-1//2
线程的执行顺序
多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程就进入了就绪状态(Runnable),等待调度。而线程调度将自行选择一个线程执行。
下面的代码只能保证每一个线程都运行完整个run函数,但是线程的启动顺序,run函数中每次循环的执行顺序都不一样
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' // '+str(i)
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
#输出
I'm Thread-1 // 0
I'm Thread-2 // 0
I'm Thread-4 // 0
I'm Thread-3 // 0
I'm Thread-5 // 0
I'm Thread-1 // 1
I'm Thread-2 // 1
I'm Thread-4 // 1
I'm Thread-3 // 1
I'm Thread-5 // 1
I'm Thread-3 // 2
I'm Thread-4 // 2
I'm Thread-1 // 2
I'm Thread-2 // 2
I'm Thread-5 // 2
总结
- 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
- 当线程的run()方法结束时该线程完成。
- 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
二、多线程-共享全局变量
但是要是前一个线程所用的时间比较长,那么可能后一个函数会提前运行...
缺点就是改变全局变量坑会造成多线程之间的混乱...
import time
from threading import Thread
g_num = 100
def work1():
global g_num
for i in range(5):
g_num = g_num + 1
print("----in work 1,g_num is %d" % g_num)
def work2():
global g_num
print("----in work 2,g_num is %d" % g_num)
print("在创建线程之前 g_num is %d" % g_num)
t1 = Thread(target=work1)
t1.start()
#延时一会,保证t1线程中的事情做完
time.sleep(1)
t2 = Thread(target=work2)
t2.start()
#输出
在创建线程之前 g_num is 100
----in work 1,g_num is 105
----in work 2,g_num is 105
传递参数
from threading import Thread
import time
def work1(nums,a):
nums.append(a)
print("----in work1---",nums)
def work2(nums):
#延时一会,保证t1线程中的事情做完
time.sleep(1)
print("----in work2---",nums)
g_nums = [11,22,33]
t1 = Thread(target=work1, args=(g_nums,20))
t1.start()
t2 = Thread(target=work2, args=(g_nums,))
t2.start()
#输出
----in work1--- [11, 22, 33, 20]
----in work2--- [11, 22, 33, 20]
多线程-共享全局变量问题
如果多个线程同时对一个局部变量进行操作,会出现资源竞争问题,从而导致数据会不准确
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
但是由于是多线程同时操作,有可能出现下面情况:
在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0。然后t2对得到的值进行加1并赋给g_num,使得g_num=1。然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
import threading
import time
g_nums = 0
def work1(nums):
global g_nums
for i in range (nums):
g_nums = g_nums + 1
print("----in work1---",g_nums)
def work2(nums):
global g_nums
for i in range (nums):
g_nums = g_nums + 1
print("----in work2---",g_nums)
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print('所有进程结束')
#输出
----in work1--- 1078248
----in work2--- 1522266
所有进程结束
三、同步、互斥锁
同步就是协同步调,按预定的先后顺序进行运行,有先后顺序
那如何来实现,同时来解决上面那个错误,可以利用线程同步来解决...上锁!
互斥锁
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
import threading
import time
g_nums = 0
mutex = threading.Lock()
def work1(nums):
global g_nums
for i in range (nums):
mutex.acquire()
g_nums = g_nums + 1
mutex.release()
print("----in work1---",g_nums)
def work2(nums):
global g_nums
for i in range (nums):
mutex.acquire()
g_nums = g_nums + 1
mutex.release()
print("----in work2---",g_nums)
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print('所有进程结束')
#输出
----in work1--- 1955581
----in work2--- 2000000
所有进程结束
我们可以试着这样理解,从1955581到2000000的计算都是第二个线程完成的
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。