线程入门
目录
一、线程
初识别线程
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
在工厂中, 每个车间都有房子,而且每个车间默认就有一条流水线.
操作系统 ---> 工厂
进程 ---> 车间
线程 ---> 流水线
cpu ---> 电源
线程:cpu最小的执行单位
进程:资源集合/资源单位.
线程运行 = 运行代码
进程运行 = 各种资源 + 线程
右键运行程序:申请内存空间,先把解释器丢进去并且把代码丢进去(进程做的),运行代码(线程)
进程和线程的区别:
过程描述的区别:
-
线程 --> 单指代码的执行过程
-
进程--> 资源的申请与销毁的过程
内存空间的却别:
-
进程内存空间彼此隔离
-
同一个进程下的超线程共享资源
进程和线程的创建速度:
- 进程需要是年轻资源开辟空间:慢
- 线程只是告诉操作系统一个执行方案:快
二、线程开启的两种方式
方式一:
from threading import Thread
def task():
print('子线程...')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print('主')
方式二:
from threading import Thread
class Task(Thread):
def run(self):
pass
if __name__ == '__main__':
t = Task()
t.start()
print('主')
三、子线程和子进程的创建速度
进程和线程的创建速度:
开启子进程需要申请资源开辟空间: 慢
开启子线程只是告诉操作系统一个执行方案: 快
开启子线程的打印效果:
# 子线程 is running
# 主
# 子线程 is end
开启子进程打印效果:
# 主
# 子进程 is running
# 子进程 is end
代码:
from threading import Thread
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(2)
print(f'{name} is end')
if __name__ == '__main__':
t = Thread(target=task, args=('子线程',))
p = Process(target=task, args=('子进程',))
# t.start()
p.start()
print('主')
四、子线程共享资源
from threading import Thread
import time, os
x = 100
def task():
global x
x = 50
print(os.getpid()) # 5204
if __name__ == '__main__':
t = Thread(target=task)
t.start()
time.sleep(2)
print(x) # 50
print(os.getpid()) # 5204
五、线程的join方法
from threading import Thread
import time
def task(name, n):
print(f'{name} start')
time.sleep(n)
print(f'{name} end')
if __name__ == '__main__':
t1 = Thread(target=task, args=('线程1', 1))
t2 = Thread(target=task, args=('线程2', 2))
t3 = Thread(target=task, args=('线程3', 3))
start = time.time()
t1.start()
t2.start()
t3.start()
t1.join() # 1s
t2.join() # 2s
t3.join() # 3s
end = time.time() # 3.0039877891540527
print(end - start)
六、了解进程的join
from multiprocessing import Process
from threading import Thread
import time
def task():
print('进程 开启')
time.sleep(10)
print('进程 结束')
def taks2():
print('子线程 开启')
time.sleep(2)
print('子线程 结束')
if __name__ == '__main__':
p = Process(target=task)
t = Thread(target=task2)
t.start() # 开线程
p.start() # 开进程
print('子进程join开始')
p.join() # 主进程的主线程等待子进程运行结束
print('主')
七、线程相关的其他方法(了解)
# Thread实例对象的方法
isAlive(): # 返回线程是否活动的。
getName(): # 返回线程名。
setName(): # 设置线程名
threading模块提供的一些方法:
threading.currentThread(): # 返回当前的线程变量
threading.enumerate(): # 返回一个包含正在运行的线程的list,正在运行指线程启动后、结束 前,不包括启动前和终止后的线程。
threading.activeCount(): # 返回正在运行的线程数量,与len(threading.enumerate()) 有相同的效果
八、守护线程(了解)
守护线程首先是一个线程
- 守护线程守护到当前进程运行结束。
- ps:比如有未完成的子进程阶段会守护,比如有未完成的其他子线程也均会守护。
守护进程首先是一个进程
- 守护进程守护到当前进程的最后一行代码结束。
# 守护线程守护到当前进程结束
from threading import Thread
import threading
import time
def threadtask(name):
print(f'{name} start')
print(time.sleep(20))
print(f'{name} end')
def threadtask2(name):
print(f'{name} start')
time.sleep(10)
print(threading.enumerate()) # [<_MainThread(MainThread, stopped 14544)>, <Thread(Thread-1, started daemon 13676)>, <Thread(Thread-2, started 13148)>]
print(f'{name} end')
if __name__ == '__main__':
t = Thread(target=threadtask, args=('守护线程',))
t2 = Thread(target=threadtask2, args=('子线程',))
t.daemon = True
t.start()
t2.start()
print('主')
'''
守护线程 start
子线程 start
主
[<_MainThread(MainThread, stopped 15448)>, <Thread(Thread-1, started daemon 17520)>, <Thread(Thread-2, started 10356)>]
子线程 end
'''
# 可以看到当主线程已经结束的时候,其他子线程没有结束的时候打印当前的活跃的线程发现有 守护线程。
九、GIL全局解释器锁
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然Python解释器中可以"运行"多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按以下方式执行:
- 设置GIL;
- 切换到一个线程去运行;
- 运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
- 把线程设置为睡眠状态;
- 解锁GIL;
- 再次重复以上所有步骤。
在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。