一、什么是线程
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程
车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线 流水线的工作需要电源,电源就相当于cpu
所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
开启线程的两种方式
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() print('主线程')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread import time class Sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print('%s say hello' % self.name) if __name__ == '__main__': t = Sayhi('egon') t.start() print('主线程')
二、线程进程的区别(挺重要)
线程 和进程的区别
进程是一个资源单位,一个进程可以包含多个线程,多个线程之间数据可以共享,线程开销比进程小
在多线程中CPU的切换速度会非常快 但资源消耗没有进程高
英文锻炼:
1、Threads share the address space of the process that created it; processes have their own address space. 2、Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process. 3、Threads can directly communicate with other threads of its process; processes must use interprocess communication to communicate with sibling processes. 4、New threads are easily created; new processes require duplication of the parent process. 5、Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes. 6、Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
多线程指的是,在一个进程中开启多个线程,简单的讲:如果多个任务共用一块地址空间,那么必须在一个进程内开启多个线程。详细的讲分为4点: 1. 多线程共享一个进程的地址空间 2. 线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用 3. 若多个线程都是cpu密集型的,那么并不能获得性能上的增强,但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。 4. 在多cpu系统中,为了最大限度的利用多核,可以开启多个线程,比开进程开销要小的多。(这一条并不适用于python)
三、tread对象的其他属性或方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread import threading from multiprocessing import Process import os def work(): import time time.sleep(3) print(threading.current_thread().getName()) if __name__ == '__main__': #在主进程下开启线程 t=Thread(target=work) t.start() print(threading.current_thread().getName()) print(threading.current_thread()) #主线程 print(threading.enumerate()) #连同主线程在内有两个运行的线程 print(threading.active_count()) print('主线程/主进程')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
MainThread <_MainThread(MainThread, started 140735268892672)> [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>] 主线程/主进程 Thread-1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.start() t.join() print('主线程') print(t.is_alive())
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
egon say hello
主线程
False
四、守护线程
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
1、对主进程来说,运行完毕指的是主进程代码运行完毕
2、对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) t.setDaemon(True) #必须在t.start()之前设置 t.start() print('主线程') print(t.is_alive())
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
主线程
True
五、GIL全局解释器锁(特别之处 记住)
什么是GIL? 全局解释器锁 仅存在与Cpython
为什么需要它? 在同一时间只有一个线程在使用解释器
如果程序中只有一个线程还需要吗? 解释器会自己启动垃圾回收机制,也会造成解释器的竞争问题
例如 GC发现变量x引用计数为0 正准备清扫 CPU突然切换到了另一个线程a
a拿着x进行使用 在使用的过程中 又切换到了GC GC接着把X指向的空间进行释放
这样一来a中的x就无法使用了 GIL将分配内存回收内存相关的操作加了锁
GIL无法避免自定义的线程中的数据竞争问题
GIL带来的问题?
加锁虽然保证了数据的安全 但是降低了性能 在多CPU的机器上 无法利用多核提升效率
其他线程要想执行 必须等到之前的线程释放了GIL 这就意味着 同一时间只有一个线程在运行
为什么不用其他解释器?
因为cpython是c语言实现 可以无缝对接c现有的所有库 就是很多现成的功能
GIL 和 自定义互斥锁的异同点
相同点:都是互斥锁 争抢执行权是无序的 执行被锁定的代码时有序的
不同点:GIL锁的是解释器的数据 自定义互斥锁所得是用户自定义的数据
GIL的加锁与解锁 是自动执行的
自动释放的时间点: io/代码执行完毕 同一线程执行时间过长3ms(py3中)
执行的字节码指令数量达到一定值(py2中)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread,Lock import os,time def work(): global n lock.acquire() temp=n time.sleep(0.1) n=temp-1 lock.release() if __name__ == '__main__': lock=Lock() n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全,不加锁则结果可能为99
六、死锁现象与递归锁
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('