Python并发编程基础 №⑤ 线程理论、Threading(线程)模块、全局解释锁、Treading中的其他方法
线程理论
能独立运行的基本单位——线程(Threads)。
注意:进程是资源分配的最小单位,线程是CPU调度的最小单位.每一个进程中至少有一个线程。
进程和线程的关系
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程操作系统中,进程不是一个可执行的实体。
线程的特点
在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)共享进程资源。
线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程id,这意味着,线程可以访问该进程的每一个内存资源;
此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
4)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与
外围设备并行工作的能力。
# 使用线程的实际场景
开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,
因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
内存中的线程
多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程。
而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。
不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,
一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。
类似于进程,每个线程也有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。
线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:
1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程
2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?
因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。
线程和python
1、全局解释器锁GIL
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。
虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
a、设置 GIL;
b、切换到一个线程去运行;
c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
d、把线程设置为睡眠状态;
e、解锁 GIL;
d、再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,
所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
2、python线程模块的选择
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。
thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用
于多个线程之间共享数据的队列数据结构。
避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与
threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主
线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,
守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,
在进程退出的时候,不用等待这个线程退出。
3、threading模块
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍
(1)线程的创建threading.Thread类
1 # 方式一: 2 import os, time 3 from threading import Thread 4 5 6 def func(n): 7 time.sleep(1) 8 print(os.getpid(), n * n) # ,每个线程都跟主进程的pid一样 9 10 print('主进程id:', os.getpid()) 11 12 # for i in range(1, 5): # 在主进程下开启多个线程 13 # thread = Thread(target=func, args=(i,)) 14 # thread.start() 15 16 # 方式二: 17 class MyThread(Thread): 18 # 传参时,必须重写Thread类的初始化方法 19 def __init__(self, arg): 20 super().__init__() 21 self.arg = arg 22 23 def run(self): 24 print('tom') 25 for i in range(1, 5): 26 func(i) 27 28 # my_thread = MyThread('tom') 29 # my_thread.start()
(2)、多线程与多进程
# 开启效率的较量 与内存数据共享
1 from multiprocessing import Pool 2 3 def add(x): 4 global total_num # 线程中,可以内存数据共享 5 print(total_num + x) 6 7 if __name__ == '__main__': 8 total_num = 100 # 内存数据共享 9 # start = time.time() 10 # pool = Pool(7) 11 # pool.map(add, range(100)) 12 # t1 = time.time() - start 13 14 sec_time = time.time() 15 thread_lst = [] 16 for i in range(100): 17 thread = Thread(target=add, args=(i, )) 18 thread.start() 19 thread_lst.append(thread) 20 [thread.join() for thread in thread_lst] 21 t2 = time.time() - sec_time 22 23 # print(t1, t2) # 0.22891592979431152 0.14960026741027832 todo 可以看到即使使用了进程池,也没线程快!
(3)、Thread类的其他方法
Thread.is_alive() # 返回线程是否活动的。
Thread.isDaemon() # 是否是守护线程
Thread.setDaemon() # 设置守护线程
Thread.setName() # 设置线程名
Thread.getName() # 返回线程名。
属性
Thread.daemon # 守护线程
Thread.name
Thread.ident
(4)、threading模块的一些方法:
threading.activeCount() # 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
threading.enumerate() # 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.current_thread() # 返回当前的线程变量。
threading.get_ident() # 返回线程id
threading.main_thread() # 返回当前的主线程变量。
1 def work(): 2 pass 3 4 t = Thread(target=work) 5 t.start() 6 t.join() 7 8 print(threading.active_count()) 9 print(threading.get_ident()) 10 print(threading.main_thread().getName()) 11 print(threading.current_thread()) # <_MainThread(MainThread, started 10828)>
使用线程实现socket的tcp聊天
服务器端
1 import socket 2 from threading import Thread 3 4 5 # 子线程 6 def chat(conn): 7 conn.send(b'hello') 8 print(conn.recv(1024).decode('utf-8')) 9 conn.close() 10 11 12 # 主线程 13 sk = socket.socket() 14 sk.bind(('127.0.0.1', 8888)) 15 sk.listen() 16 17 while 1: 18 conn, addr = sk.accept() 19 Thread(target=chat, args=(conn, )).start() 20 21 sk.close()
客户端
1 import socket 2 3 sk = socket.socket() 4 sk.connect(('127.0.0.1', 8888)) 5 6 print(sk.recv(1024).decode('utf-8')) 7 msg = input('>>>').encode('utf-8') 8 sk.send(msg) 9 10 sk.close()

浙公网安备 33010602011771号