什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
一个线程是一个执行上下文,它是一个CPU需要执行一系列指令的所有信息。
假设你正在读一本书,你现在想休息一下,但是你希望能够回来,从你停止的地方继续阅读。实现这一目标的一种方法是记下页码、行号和字号。所以你读一本书的执行上下文是这三个数字。
如果你有一个室友,而且她使用的是同样的技术,她可以在你不用的时候拿着书,然后从她停止的地方继续阅读。然后你可以把它拿回来,从你所在的地方重新开始。
线程的工作方式相同。一个CPU给你的错觉是它同时在做多个计算。它通过在每个计算上花费一点时间。它可以这样做,因为它对每个计算都有一个执行上下文。就像你可以和你的朋友分享一本书一样,许多任务可以共享一个CPU。
在技术层面上,执行上下文(因此是一个线程)由CPU寄存器的值组成。
最后:线程与进程不同。线程是执行的上下文,而进程是与计算相关的一堆资源。一个进程可以有一个或多个线程。
说明:与进程相关的资源包括内存页(进程中的所有线程都具有相同的内存视图)、文件描述符(例如,打开的套接字)和安全凭据(例如启动进程的用户的ID)。
什么是进程(process)?
程序的执行实例称为进程。
每个进程提供执行程序所需的资源。进程具有虚拟地址空间、可执行代码、对系统对象的打开句柄、安全上下文、惟一进程标识符、环境变量、优先级、最小和最大工作集大小,以及至少一个执行线程。每个进程都是从一个线程开始的,通常称为主线程,但是可以从它的任何线程创建额外的线程。
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
线程和进程的区别?
- 线程共享创建它的进程的地址空间;进程有自己的地址空间。
- 线程可以直接访问其进程的数据段;进程有自己的父进程数据段的副本。
- 线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与同胞进程通信。
- 新线程很容易创建;新进程需要父进程的重复。
- 线程可以对相同进程的线程进行相当大的控制;进程只能对子进程进行控制。
- 对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。
Python GIL(Global Interpreter Lock)全局解释器锁
在CPython中,全局解释器锁(或GIL)是一个互斥锁,可以防止多个本机线程同时执行Python字节码。这一锁定主要是因为CPython的内存管理不是线程安全的。(然而,由于GIL的存在,其他特征已经发展到依赖于它所赋予的保证。)
这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
Python threading(线程)模块
直接调用:

1 import threading 2 import time 3 4 def func(n): #定义每个线程要运行的函数 5 6 print("running on number:%s" % n) 7 8 time.sleep(3) 9 10 if __name__ == '__main__': 11 12 t1 = threading.Thread(target=func,args=(1,)) #生成一个线程实例 13 t2 = threading.Thread(target=func,args=(2,)) #生成另一个线程实例 14 15 t1.start() #启动线程 16 t2.start() #启动另一个线程 17 18 print(t1.getName()) #获取线程名 19 print(t2.getName())
继承式调用:

1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self,n): 7 threading.Thread.__init__(self) 8 self.num = n 9 10 def run(self):#定义每个线程要运行的函数 11 12 print("running on number:%s" %self.n) 13 14 time.sleep(2) 15 16 if __name__ == '__main__': 17 18 t1 = MyThread(1) 19 t2 = MyThread(2) 20 t1.start() 21 t2.start()
有些线程执行后台任务,比如发送keepalive数据包,或者执行周期性的垃圾收集,等等。这些只在主程序运行时有用,并且可以在其他非守护进程退出时将它们删除。
如果没有守护线程,您必须跟踪它们,并告诉它们退出,然后程序才能完全退出。通过将它们设置为守护线程,您可以让它们运行并忘记它们,当程序退出时,任何守护线程都会自动被杀死。

1 def run(n): 2 print('[%s] running on ' % n) 3 time.sleep(2) 4 print('--done--') 5 6 7 def main(): 8 for i in range(5): 9 t = threading.Thread(target=run, args=[i, ]) 10 t.start() 11 t.join(1) 12 print('starting thread', t.getName()) 13 14 15 m = threading.Thread(target=main, args=[]) 16 # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 17 m.setDaemon(True) 18 m.start() 19 m.join(timeout=2) 20 print("---main thread done----")
start 线程准备就绪,等待CPU调度
setName 为线程设置名称
getName 获取线程名称
setDaemon 设置为后台线程或前台线程(默认)
如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join 逐个执行每个线程,执行完毕后继续往下执行
run 线程被cpu调度后自动执行线程对象的run方法
注意:守护程序线程突然停在关闭。它们的资源(如打开文件、数据库事务等)可能无法正常释放。如果您希望您的线程优雅地停止,那么让它们成为非daemonic,并使用一个适当的信号机制,例如事件。
线程锁(Lock、RLock(递归锁))
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,就会得到错误的数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。
*注:我用的Python3,不知为什么,结果总是正确的,网上搜了搜,说可能是自动加了锁:以后可能会用Python2版本,也标注一下

1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 15 15 16 16 17 17 18 18 19 19 20 20 21 21 22 import time 23 import threading 24 25 def addNum(): 26 global num #在每个线程中都获取这个全局变量 27 print('--get num:',num ) 28 time.sleep(1) 29 num +=1 #对此公共变量进行-1操作 30 31 num = 0 #设定一个共享变量 32 thread_list = [] 33 for i in range(100): 34 t = threading.Thread(target=addNum) 35 t.start() 36 thread_list.append(t) 37 38 for t in thread_list: #等待所有线程执行完毕 39 t.join() 40 41 42 print('final num:', num )
不加锁的听说Python2每次运行结果不都是100.

1 import time 2 import threading 3 4 def addNum(): 5 global num #在每个线程中都获取这个全局变量 6 print('--get num:',num ) 7 time.sleep(1) 8 lock.acquire() #修改数据前加锁 9 num +=1 #对此公共变量进行-1操作 10 lock.release() #修改后释放 11 12 num = 0 #设定一个共享变量 13 thread_list = [] 14 lock = threading.Lock() #生成全局锁 15 for i in range(100): 16 t = threading.Thread(target=addNum) 17 t.start() 18 thread_list.append(t) 19 20 for t in thread_list: #等待所有线程执行完毕 21 t.join() 22 23 print('final num:', num )
Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 。
既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
死锁:指多个循环等待他方占有得的资源而无线期僵持下去的局面

死锁 class MyThread(threading.Thread): def doA(self): lA.acquire() # lC.acquire() print(self.name,time.ctime()) time.sleep(2) lB.acquire() # lC.acquire() print(self.name, time.ctime()) lB.release() lA.release() #lC.release() # lC.release() def doB(self): lB.acquire() #lC.acquire() print(self.name, time.ctime()) time.sleep(2) lA.acquire() # lC.acquire() print(self.name, time.ctime()) lA.release() lB.release() #lC.release() # lC.release() def run(self): self.doA() self.doB() if __name__ == '__main__': lA = threading.Lock() lB= threading.Lock() # lC = threading.RLock() thread_list = [] for i in range(5): thread_list.append(MyThread()) for i in thread_list: i.start() for i in thread_list: i.join()
可以用RLock代替Lock
信号量(Semaphore)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据

1 import threading,time 2 3 def run(n): 4 semaphore.acquire() 5 time.sleep(1) 6 print("run the thread: %s" %n) 7 semaphore.release() 8 9 if __name__ == '__main__': 10 11 num= 0 12 semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行 13 for i in range(20): 14 t = threading.Thread(target=run,args=(i,)) 15 t.start()
Event(事件)
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
- clear:将“Flag”设置为False
- set:将“Flag”设置为True

1 import threading 2 3 def do(event): 4 print('start') 5 event.wait() 6 print('excute') 7 8 event_obj = threading.Event() 9 for i in range(10): 10 t = threading.Thread(target=do,args=(event_obj,)) 11 t.start() 12 #event_obj.clear() 13 inp =input('输入:') 14 if inp =='true': 15 event_obj.set()
通过Event来实现两个或多个线程间的交互:

1 import time 2 import random 3 import threading 4 5 def light(): 6 if not event.is_set(): 7 event.set() #wait就不阻塞 8 count = 0 9 while True: 10 if count < 10: 11 print('