python网络编程–线程
什么是线程
前面说过了进程,进程最少有一个线程。进程是计算机中最小的资源分配单位,线程是cpu调度的最小单位。
什么意思呢?简单来说就是你要运行的一个程序,进程是一个代码仓库给线程分配资源的,要想让程序运行起来,cpu就找到线程让线程去工作。
再打个比方,你老板要实现一个功能,需要你来完成,总不可能说你的手去给我写这个功能吧。只能命令你让你给你的手分配工作去完成。大概就这种情况。
进程和线程的关系
线程与进程的区别可以归纳为以下4点:
- 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
- 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
- 在多线程操作系统中,进程不是一个可执行的实体。
线程的特点
在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
TCB包括以下信息
1)线程状态。 2)当线程不运行时,被保存的现场资源。 3)一组执行堆栈。 4)存放每个线程的局部变量主存区。 5)访问同一个进程中的主寸和其他资源。
用于被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
独立调度和分派的基本单位
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
共享进程资源
线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
可并发执行
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
理论知识
全局解释器锁GIL
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
a、设置 GIL;
b、切换到一个线程去运行;
c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
d、把线程设置为睡眠状态;
e、解锁 GIL;
d、再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
python线程模块的选择
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
threading模块
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍(官方链接)
线程的创建Threading.Thread类
线程的创建
用函数式创建线程
import os
import time
from threading import Thread
# multiprocessing 是完全仿照这threading的类写的
def func():
print('start son thread')
time.sleep(1)
print('end son thread',os.getpid())
# 启动线程 start 线程可以不用写 if ...main...
Thread(target=func).start()
print('start',os.getpid())
time.sleep(0.5)
print('end',os.getpid())
用类方法创建线程
import os
import time
from threading import Thread
# multiprocessing 是完全仿照这threading的类写的
class Mythread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print('%s hello thread'%self.name,self.ident) # self.ident 线程号
# 启动线程 start
t = Mythread('wuye')
t.start()
print('主线程',t.ident)
多线程的创建
# 主线程什么时候结束?等待所有子线程结束之后才结束
# 主线程如果结束了,主进程也就结束了
创建多线程
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread',i,os.getpid())
for i in range(10):
Thread(target=func,args=(i,)).start()
print('main')
join方法
# join方法 阻塞 直到子线程执行结束
def func(i):
print('start son thread',i)
time.sleep(1)
print('end son thread',i,os.getpid())
t_l = []
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
t_l.append(t)
for t in t_l:t.join()
print('子线程执行完毕')
Thread类的其它方法
Thread实例对象的方法
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
示例
from threading import current_thread,enumerate,active_count
def func(i):
t = current_thread()
print('start son thread',i,t.ident)
time.sleep(1)
print('end son thread',i,os.getpid())
t = Thread(target=func,args=(1,))
t.start()
print(t.ident)
print(current_thread().ident) # 在哪一个线程里,current_thread()得到的就是这个当前线程的信息
print(enumerate()) # enumerate 检测存活的线程类,返回成列表
print(active_count()) # =====len(enumerate())
守护线程
# 守护线程一直等到所有的非守护线程都结束之后才结束
# 当主线程代码执行完毕守护线程也随之结束
# 除了守护了主线程的代码之外也会守护子线程
守护线程
import time
from threading import Thread
def son1():
while True:
time.sleep(0.5)
print('in son1')
def son2():
for i in range(5):
time.sleep(1)
print('in son2')
t =Thread(target=son1)
t.daemon = True
t.start()
Thread(target=son2).start()
time.sleep(3)
小测试
进程线程的效率差
def func(a,b):
c = a+b
import time
from multiprocessing import Process
from threading import Thread
if __name__ == '__main__':
start = time.time()
p_l = []
for i in range(100):
p = Process(target=func,args=(i,i*2))
p.start()
p_l.append(p)
for p in p_l:p.join()
print('process :',time.time() - start)
start = time.time()
p_l = []
for i in range(100):
p = Thread(target=func, args=(i, i * 2))
p.start()
p_l.append(p)
for p in p_l: p.join()
print('thread :',time.time() - start)
数据隔离还是共享
from threading import Thread
n = 100
def func():
global n # 不要在子线程里随便修改全局变量
n-=1
t_l = []
for i in range(100):
t = Thread(target=func)
t_l.append(t)
t.start()
for t in t_l:t.join()
print(n)