活动地址:CSDN21天学习挑战赛
平时唱歌跳舞都是一起进行的,如果先唱歌后跳舞,或者先跳舞后唱歌,感觉很不协调,别扭—所以需要一个多任务
案例:
from time import sleep
def sing():
for i in range(3):
print("正在唱歌....%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞....%d"%i)
sleep(1)
if __name__ == '__main__':
sing()
dance()
概念:多任务就是可以一边听歌,一边上网冲浪,或者在干点其它什么的,可以同时有2个以上的任务在执行
注意:
并发:某个时间段内,多个任务交替执行。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态(暂停)。
并行:同一时间处理多任务的能力,多有多个线程在操作时,CPU同时处理这些线程请求的能力。
Python的thread模块是比较底层的,Python的threading模块是对thread做了包装,使用更加方便。
threading常用方法:
方法名 | 解释 |
---|---|
threading.active_count() | 返回当前处于active状态的Thread对象 |
threading.current_thread() | 返回当前Thread对象 |
threading.get_ident() | 返回当前线程的线程标识符,线程标识符是一个非负整数,并无特殊含义,知识用来标识线程,该证书可能会被循环利用。Python3.3版本后支持该方法 |
threading.enumerate() | 返回当前处于active解释器的线程对象。Python3.4版本以后支持该方法 |
threading.main_thread() | 返回主线程对象,即启动Python解释器的线程对象。Python3.4版本以后支持该方法 |
threading.stack_size() | 返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(标识使用系统默认值)或大于32K正整数 |
threading模块提供了Thread、Lock、RLock、Conditon、Event、Timer和Semaphore等类来支持多线程,Thread是其中最重要也是最基本的一个类,通过该类创建线程并控制线程的运行。
使用Thread创建线程的方法:
from time import sleep
import datetime
def sing():
print("正在唱歌....")
sleep(1)
if __name__ == '__main__':
for i in range(3):
sing()
print(datetime.datetime.now())
import threading
from time import sleep
import datetime
def sing():
print("正在唱歌....")
sleep(1)
if __name__ == '__main__':
for i in range(3):
t = threading.Thread(target=sing)
t.start()
print(datetime.datetime.now())
import threading
from time import sleep, ctime
def sing():
for i in range(3):
print("正在唱歌....%d" % i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞....%d" % i)
sleep(1)
if __name__ == '__main__':
print("开始:", ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
print("结束: ", ctime())
import threading
from time import sleep, ctime
def sing():
for i in range(3):
print("正在唱歌....%d" % i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞....%d" % i)
sleep(1)
if __name__ == '__main__':
print("开始:", ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
num = len(threading.enumerate())
print("线程数: %d" % num)
if (num) < 1:
break
sleep(1)
print("结束: ", ctime())
使用threading线程模块封装时,定义一个新的子类class,继承threading.Thread,重写run方法
import threading
from time import sleep
class MyClass(threading.Thread):
def run(self):
for i in range(3):
sleep(1)
msg = self.name + "--->" + str(i)
print(msg)
if __name__ == '__main__':
t = MyClass()
t.start()
Python的threading.Thread类有一个run方法,定义线程功能函数,可以覆盖该方法,创建自己的线程实例后,调用start启动,交给虚拟机进行调度,有执行机会就会调用run方法执行线程。
import threading
from time import sleep
class MyClass(threading.Thread):
def run(self):
for i in range(3):
sleep(1)
msg = self.name + "--->" + str(i)
print(msg+'\n')
def test():
for i in range(5):
t = MyClass()
t.start()
if __name__ == '__main__':
test()
多线程执行顺序是不确定的,sleep时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。线程调度选择一个线程执行。以上代码能保证运行每个线程都运行完整的run函数,但是线程启动顺序、run函数每次循环执行的顺序不能确定。
总结:
from threading import Thread
from time import sleep
gl_num = 10
def sing():
global gl_num
for i in range(3):
gl_num += 1
print("sing------>%d"%gl_num)
def dance():
global gl_num
print("sing------>%d" % gl_num)
t1 = Thread(target=sing)
t1.start()
sleep(1)
t2 = Thread(target=dance)
t2.start()
列表当做参数传递:
from threading import Thread
from time import sleep
gl_num = [10, 20, 30]
def sing(num):
num.append(33)
print("sing------>", num)
def dance(num):
sleep(1)
print("sing------>", num)
t1 = Thread(target=sing, args=(gl_num,))
t1.start()
t2 = Thread(target=dance, args=(gl_num,))
t2.start()
某个线程需要修改共享数据时,先将其锁定,此时资源状态为锁定,其它线程不允许修改;直到该线程释放资源,将资源状态调整非锁定,其它线程才能再次锁定该共享数据。互斥锁保证每次只有一个线程进行数据修改,能够保证多线程数据正确性。
import threading
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 解锁
mutex.release()
调用acquire之前没有锁,不会堵塞;已锁,会堵塞,直到这个锁解锁
案例:互斥锁两个线程对统一变量累加10万次
import threading
from time import sleep
gl = 0
mutex = threading.Lock()
def sing(num):
global gl
for i in range(num):
mutex.acquire()
gl += 1
mutex.release()
print("唱歌-----> %d" % gl)
def dance(num):
global gl
for i in range(num):
mutex.acquire()
gl += 1
mutex.release()
print("跳舞------>%d" % gl)
t1 = threading.Thread(target=sing, args=(100000,))
t1.start()
t2 = threading.Thread(target=dance, args=(100000,))
t2.start()
while len(threading.enumerate()) != 1:
sleep(1)
print("最终结果: %d" % gl)
锁的拆解过程:
- 当一个线程调用acquire()方法时锁定,状态是locked
-只允许有一个线程获得锁,另一个线程想使用,该线程变成blocked状态,阻塞,直至有锁的线程调用release()方法释放锁,此时变成unlocked状态。
-线程调度从阻塞线程中选择一个来调用锁,并使此线程进入运行running状态。
总结:
锁好处:确保了代码可以由某一线程从开始到结束独立运行
锁坏处:阻止了多线程并发执行,单线程执行效率低下;使用多个锁可能会造成死锁
解释:线程间共享多个资源时,两个线程并各站一部分资源,同时等待对方释放资源,会造成死锁。
如何避免死锁: