线程
1. 什么是线程
线程就是进程里面的执行单位,每一个进程肯定都自带一个线程,真正被cpu执行的是线程,线程是代码的执行过程,该过程中需要的资源都找对应的进程要
进程是资源的单位,线程是执行单位!
补充:同一个进程里面的多个线程资源是共享的!
2. 为啥要有线程
一个进程里面可以开设多个线程,而开设线程是不需要申请内存空间的(进程需要),因此,开设线程的消耗远远小于进程!
3. 如何使用线程
3.1 创建线程的第一种方式(掌握)
from threading import Thread # 导入线程模块
from multiprocessing import Process
import time
# 定义子线程函数
def func(name):
print('%s 来啦'%name)
time.sleep(2)
print('%s走了'%name)
# 创建线程不需要写在main下,但是我们还是习惯性的把它写在main下
if __name__ == '__main__':
# 创建线程对象
t = Thread(target=func,args=('zhang',))
# p = Process(target=func,args=('zhang',))
# p.start()
t.start() # 启动线程
# 线程和进程的开启相比,线程更加的块,线程不需要开辟内存空间
print('主')
3.2 创建线程的第二种方式
from threading import Thread
# 1.创建一个类,该类继承线程类
class MyThread(Thread):
# 第二种方法需要给子线程添加参数的方法是类中有一个双init方法
# 先继承父类的双init方法,然后在书写需要自己加入的参数,这样就可以在run函数里调用参数了
def __init__(self,name):
super().__init__()
self.name = name
# 2.该类中子线程函数名必须是run
def run(self):
print('我是线程%s'%self.name)
if __name__ == '__main__':
# 3.创建子线程对象
t = MyThread('zhang')
# 4.开启子线程
t.start()
print('主')
3.3 线程中的join方法
join方法的含义是:主线程等待子线程运行完毕再执行
from threading import Thread # 导入线程模块
from multiprocessing import Process
import time
def func(name):
print('%s 来啦'%name)
time.sleep(1)
print('%s走了'%name)
if __name__ == '__main__':
t = Thread(target=func,args=('zhang',))
t.start()
t.join() # join方法的含义是:主线程等待子线程运行完毕再执行
print('主')
3.4 线程对象和其他方法
from threading import Thread,active_count,current_thread
import time,os
def func():
# os.getpid()获取当前线程的进程id,主线程和子线程id相同
# print('hello',os.getpid())
# current_thread().name获取当前线程的线程名
print('hello', current_thread().name)
time.sleep(2)
if __name__ == '__main__':
t = Thread(target=func)
t.start()
# print('主',current_thread().name)
# active_count() 获取当前活跃的线程数量
print('主',active_count())
3.5 守护线程
from threading import Thread
import time
def func(name):
print('%s来啦'%name)
time.sleep(2)
print('%s走了'%name)
if __name__ == '__main__':
t = Thread(target=func,args=('zhang',))
t.daemon = True # 守护主线程,主线程结束之后该子线程也会结束
t.start()
print('主')
"""
主线程一般不会自动结束,它会等待所有非守护线程结束之后再结束
因为主线程结束意外着主进程也结束了,这样其他线程就无法调用主进程的资源了
"""
3.6 线程的互斥锁
from threading import Thread,Lock
import time
momey = 100
# 加一把锁
metua = Lock()
def func():
global momey
# 在获取数据的前面加一把锁
metua.acquire()
num = momey
time.sleep(0.01)
momey = num -1
# 释放该锁
metua.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=func)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(momey)
3.7 GIL锁与互斥锁
GIL需要知道的重点:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******)
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势
GIL锁与互斥锁的区别
from threading import Thread,Lock
import time
momey = 100
# 加一把锁
metua = Lock()
def func():
global momey
# 在获取数据的前面加一把锁
metua.acquire()
num = momey
time.sleep(0.01) # 注意:这里sleep是在模拟网络延迟。如果假设没有网络延迟,没有互斥锁LOCK,得到money的结果也应为0,因为一个进程里有一个cpython解释器,cpython解释器有一个GIL锁的概念,同一个进程下的多个线程无法同时执行,它们必须去抢同一个GIL锁,没有网络延迟(没有IO),GIL锁就会直到数据操作完在释放,保证了多个线程执行的数据安全;但是实际上是有网络延迟的,就比如sleep,这时GIL锁就会直接释放掉,导致得到的money=99
momey = num -1
# 释放该锁
metua.release()
if __name__ == '__main__':
t_list = []
for i in range(100):
t = Thread(target=func)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(momey)
3.8多线程与多进程的比较
当需要处理的工作大都是是计算密集型时优先考虑多进程(较好的利用多核优势)
当需要处理的工作大都是IO密集型时优先考虑多线程(节省空间消耗)
计算密集型就是计算处理偏多
IO密集型就是用户输入输出偏多
# 当处理的工作是计算密集型的时候,好像现在多线程比多进程有优势???待验证!
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res = 0
for i in range(1000000):
res *= i
if __name__ == '__main__':
list = [] # 将下面for循环每次开设的线程/进程对象存储起来
print(os.cpu_count()) # 获取当前计算机的CPU个数
start_time = time.time() # 获取开始时间
for i in range(16): # for循环开设进程/线程
# p = Process(target=work)
p = Thread(target=work)
p.start()
list.append(p) # 将每次开设的进程对象添加到列表里
for p in list: # 变量进程对象
p.join() # 守护主进程/线程
print(time.time()-start_time)
# 计算密集型 多进程用时1.8412911891937256秒 多线程用时0.318603515625秒