线程
什么是线程?
1.一个程序运行起来至少有一个进程,一个进程至少有一个线程
2.处理器cpu分配给线程,即cpu真正运行的是线程
3.分配cpu资源给线程时,是通过时间片轮训方式进行的
4.进程是操作系统分配程序执行资源的单位,而线程是进程的一个实体,
是CPU调度和分配的单位。
开启多线程
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用,通过threading模块可以创建线程。
import threading
import time
def download_music():
"""模拟下载歌曲,需要5秒钟下载完成"""
for i in range(5):
time.sleep(1) # 休眠1秒
print("---正在下载歌曲%d---" % i)
def play_music():
"""模拟播放歌曲,需要5秒钟下载完成"""
for i in range(5):
time.sleep(1) # 休眠1秒
print("---正在播放歌曲%d---" % i)
def main():
# 创建线程对象t1
# target: 指向新开启的线程要执行的代码
t1 = threading.Thread(target=download_music)
t2 = threading.Thread(target=play_music)
t1.start() # 启动线程,既然线程开始执行
t2.start()
if __name__ == '__main__':
main()
运行结果:
多线程听歌程序运行结果.png说明
- 可以明显看出使用了多线程并发的操作,花费时间要短很多;
- 当调用start()时,才会真正的执行线程,执行线程中的代码。
线程何时开始和结束
1.子线程何时开启,何时运行
当调用thread.start()时 开启线程,再运行线程的代码
2.子线程何时结束
子线程把target指向的函数中的语句执行完毕后,立即结束当前子线程
3.查看当前线程数量
通过threading.enumerate()可枚举当前运行的所有线程
4.主线程何时结束
所有子线程执行完毕后,主线程才结束
import threading
import time
def test1():
for i in range(5):
time.sleep(1)
print('---子线程1---%d' %i)
print('子线程1中查看线程情况',threading.enumerate())
def test2():
for i in range(10):
time.sleep(1)
print('---子线程2---%d' %i)
print('子线程2中查看线程情况',threading.enumerate())
def main():
print('创建线程之前的线程情况',threading.enumerate())
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
time.sleep(1)
print('创建线程之后的线程执行情况',threading.enumerate())
t1.start()
t2.start()
time.sleep(1)
print('调用了thread.start()之后的线程情况',threading.enumerate())
t2.join() # 当t2线程执行完毕之后 在执行后续的代码
print('列表目前的线程',threading.enumerate())
if __name__ == '__main__':
main()
运行结果略,可以自行运行显示。
线程执行过程中的注意点
线程执行代码的封装
通过上一小节,能够看出,通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承threading.Thread就可以了,然后重写run方法。
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
msg = "I'm "+ self.name + '@' + str(i)
print(msg)
if __name__ == '__main__':
t1 = MyThread()
t1.start()
执行结果:
线程执行代码的封装.png说明:
python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。
线程的执行顺序
# coding:utf-8
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()
执行结果:
线程的执行顺序.png说明:
从代码和执行结果我们可以看出,多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。上面的代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。
总结
1 每个线程默认有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
2 当线程的run()方法结束时该线程完成。
3 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
多线程共享全局变量
总结
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
示例代码
from threading import Thread
import time
g_num = 100
def work1():
global g_num
for i in range(3):
g_num += 1
print('---in work1,g_num is %d---' %g_num)
def work2():
global g_num
print('---in work2,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()
运行结果
11.png列表当做实参传递到线程中
from threading import Thread
import time
def work1(nums):
nums.append(44)
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,))
t1.start()
t2 = Thread(target=work2,args=(g_nums,))
t2.start()
22.png
多线程-共享全局变量的问题
多线程开发可能会遇到的问题
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
但是由于是多线程同时操作,有可能出现下面情况:
1.在g_num=0时,t1取得g_num=0。此时系统把t1调度为”sleeping”状态,把t2转换为”running”状态,t2也获得g_num=0
2.然后t2对得到的值进行加1并赋给g_num,使得g_num=1
3.然后系统又把t2调度为”sleeping”,把t1转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。
4.这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
进行一个测试代码:
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num += 1
print('---in work1,g_num is %d---' %g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print('---in work2,g_num is %d---' %g_num)
print('---线程创建之前g_num is %d---'%g_num)
t1 = threading.Thread(target=work1,args=(100,))
t1.start()
t2 = threading.Thread(target=work2,args=(100,))
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print('2个线程对同一个全局变量操作之后的最终结果是%s' %g_num)
运行结果
33.png加大num
...
t1 = threading.Thread(target=work1, args=(1000000,))
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
...
44.jpg
再次总结
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确。