前言:前面讲到了多进程,那么本博主今天来深剖实现多任务的第二种方式:线程(threading)
一.引入线程:
众所周知传统的进程有两个基本属性:
- 可拥有资源的独立单位;
- 可独立调度和分配的基本单位;
引入线程的基本原因是,进程在创建
、·切换·和·撤销·时,系统必须为之付出·较大的内存开销·,所以在系统中设置的进程数量不宜过多
,进程的切换频率不宜太高
,这就·限制·了·并发程度·的提高。引入线程后,将传统进程的两个基本属性分开,线程作为调度和分配的基本单位
,进程作为独立分配资源的单位
。用户可以创建线程来完成任务,以减少程序并发执行时付出的内存开销。
- 进程给线程目标,线程去完成这个目标,
- 一个程序执行必然有一个线程(也就是主线程),一个程序执行必然有一个进程(主进程)。
二.普通程序与线程:.
1.普通程序举例:
假定我们在执行程序时,会构造很多函数,但是如果每个函数都要完成自己的事情,在普通程序中,只有当一个完成时才会执行另一个函数,这样子会造成内存过度开销,例如:
import time
def run():
for _ in range(5):
time.sleep(1)
print("我会跑")
def sing():
for _ in range(5):
time.sleep(1)
print("我会唱歌")
if __name__ == '__main__':
run()
sing()
我们简单的定义了两个函数,各有各的事情要做,但是这种情况只有当run()
函数执行完才会去执行sing()
函数,这样程序会一直等待,会造成内存
不必要的开销
,结果如下图:
2.线程执行:
线程引入需要导包,也就是threading
这个包,thread
模块是比较底层的模块,threading
是对thread
的包装
。所以导入threading
,我们先来看一下区别,然后我们来看看线程的声明以及开启;
import threading
import time
def run():
for _ in range(5):
time.sleep(1)
print("我会跑")
def sing():
for _ in range(5):
time.sleep(1)
print("我会唱歌")
if __name__ == '__main__':
run_thread=threading.Thread(target=run)
sing_thread=threading.Thread(target=sing)
run_thread.start()
sing_thread.start()
三.线程(threading):
通过上面的举例我们都已经看到了两者的区别,线程会各干各的事,节约了不少内存空间,但是怎么使用呢--------------------------我们来看看:
1.threading.Thread():
和进程一样创建一个Thread对象
,来进行创建这个线程
,Thread
的参数有:
- group:指定进程组,一般就默认为
None
; - target:执行目标任务名;
- name:进程名字;
- args:以元组形式向执行任务传参;
- kwargs:以字典形式向执行任务传参.
- daemon:守护程序,默认值为
None
;
举例:
run_thread=threading.Thread(target=run)
sing_thread=threading.Thread(target=sing)
在前面进行定义的方法就可以在这儿通过此方法进行创建哦
2.start():
和进程中一样都是用来开启进程的,例如我们在面创建了两个线程,那么在此就可以进行开启:
run_thread.start()
sing_thread.start()
3.join():
当主线程执行完再执行子线程,和进程一样:
run_thread.start()
run_thread.join()
sing_thread.start()
四.查看线程数量:
当线程有点多的时候,我们可以通过enumerate()
来进行查看线程的数量,如下:
import threading
import time
count=0
def run():
for _ in range(5):
time.sleep(1)
print("我会跑")
print(threading.enumerate())
def sing():
for _ in range(5):
time.sleep(1)
print("我会唱歌")
print(threading.enumerate())
if __name__ == '__main__':
# run()
# sing()
run_thread=threading.Thread(target=run)
sing_thread=threading.Thread(target=sing)
run_thread.start()
sing_thread.start()
print(threading.enumerate())
我们可以看到,图中总共有三个线程,一个主线程,两个子线程,并且有
Thread-x
来进行标识
五.线程特点:
-
线程执行代码的封装,通过threading这个包中的Thread功能,往往会定义一个新的子类Class来继承:threading.thread,一般情况下线程的主入口函数为run(),我们只不过对其进行重写而已:
import threading
class threa(threading.Thread):
def run(self):
for _ in range(5):
time.sleep(1)
print("我会跑")
print(threading.enumerate())
if __name__ == '__main__':
# run()
# sing()
a=threa()
run_thread=threading.Thread(target=a.run)
run_thread.start()
ret=len(threading.enumerate())
print(ret)
-
多线程共享全局变量: 对全局变量进行修改时,判断是否对全局变量进行引用修改,如果修改了引用然后执行,让全局变量指向了另一个新地方,如果修改了引用的数据,则不必担心变量被分化:
举例:
import threading
import time
count=0
def sum():
for i in range(10000):
global count
count+=1
print(f"我是第一个:{count}")
def sum1():
for i in range(10000):
global count
count+=1
print(f"我是第二个:{count}")
if __name__ == '__main__':
# run()
# sing()
sum_thread=threading.Thread(target=sum)
sum1_thread = threading.Thread(target=sum1)
sum_thread.start()
sum1_thread.start()
# run_thread.start()
# run_thread.join()
# sing_thread.start()
print(f"总计为:{count}")
由上图可以看出,线程是共享全局变量
的,但是修改全局变量需用globa
l哦