线程理论
一 什么是线程
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源
二 线程与进程的区别
- 同一个进程内的多个线程共享该进程内的地址资源
- 创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小)
开启线程的两种方式
一 threading模块介绍
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍。
方式一
1 from threading import Thread 2 import time 3 4 def sayhi(name): 5 time.sleep(2) 6 print('%s say hello' %name) 7 8 if __name__ == '__main__': 9 t=Thread(target=sayhi,args=('egon',)) 10 t.start() 11 print('主线程')
方式二
1 #方式二 2 from threading import Thread 3 import time 4 5 class Sayhi(Thread): 6 def __init__(self,name): 7 super().__init__() 8 self.name=name 9 def run(self): 10 time.sleep(2) 11 print('%s say hello' % self.name) 12 13 if __name__ == '__main__': 14 t = Sayhi('egon') 15 t.start() 16 print('主线程')
运行代码,开启一个主进程,主进程中的主线程通过t=Thread(target=sayhi,args=('egon',)),
t.start()开启了另一个线程
多线程与多进程
同一进程内的线程都是平级的,不存在子线程之说,只有进程才存在主进程和子进程之说
开启进程,操作系统要申请内存空间,让好拷贝父进程地址空间到子进程,开销远大于线程。
from threading import Thread def work(): print('hello') if __name__ == '__main__': t=Thread(target=work) t.start() print('主线程/主进程')
hello 主线程/主进程
执行结果如下,几乎是t.start ()的同时就将线程开启了,然后先打印出了hello,证明线程的创建开销极小。
同一进程内的线程pid一样,不同进程pid不同
线程:
from threading import Thread import os def work(): print('hello',os.getpid()) if __name__ == '__main__': t1=Thread(target=work) t2=Thread(target=work) t1.start() t2.start() print('主线程/主进程pid',os.getpid())
hello 7939 hello 7939 主线程/主进程 7939
进程:
from multiprocessing import Process import os def work(): print('hello',os.getpid()) if __name__ == '__main__': p1=Process(target=work) p2=Process(target=work) p1.start() p2.start() print('主线程/主进程',os.getpid())
主线程/主进程 7951 hello 7952 hello 7953
多线程的执行本质上来说还是一个个线程按顺序来执行,但基于多道技术 ,后面发起的线程不需要等待前面的线程完全结束后才开始执行,当前面的线程遇到io阻塞或占用cpu时间过长时系统会自动切到其他的线程去(多道技术);
而之前我们所写的程序就是按顺序来执行的,执行了一个函数(以上例子的子线程也是调用函数)后,必须等待这个函数的结束return后才能执行下一个函数(无论这个函数中间有没有io阻塞或占用cpu时间过长),而多道技术不需要等到一个函数结束才能执行其他函数,依据多道技术系统会自动io阻塞和cpu占用时间来切换,可以类似于我们之前在写函数时在函数内部加一个条件,当遇到这个条件时去调用其他函数,这样实现和一个函数切换到另一个函数,但没有实现再切换回来,多道技术不仅可以让一个函数(线程)切换到另一个函数(线程),还可以再切回到原来的函数继续执行;
综上所述,当我们能实现将函数切换到另一个函数再切回来就可以模拟(多道技术)并发操作,在后面将IO模型,我们将讲述如何模拟多道技术中 遇到io阻塞时切换的情况(没有模拟占用cpu时间过长的情况)。
Thread对象的其他属性或方法
Thread实例对象的方法
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
1 from threading import Thread 2 import threading 3 from multiprocessing import Process 4 import os 5 6 def work(): 7 import time 8 time.sleep(3) 9 print(threading.current_thread().getName()) 10 11 12 if __name__ == '__main__': 13 #在主进程下开启线程 14 t=Thread(target=work) 15 t.start() 16 17 print(threading.current_thread().getName()) 18 print(threading.current_thread()) #主线程 19 print(threading.enumerate()) #连同主线程在内有两个运行的线程 20 print(threading.active_count()) 21 print('主线程/主进程')
MainThread <_MainThread(MainThread, started 15728)> [<_MainThread(MainThread, started 15728)>, <Thread(Thread-1, started 16556)>] 2 主线程/主进程 Thread-1
守护线程
守护线程会等待主线程运行完毕后被销毁
注意:运行完毕并非终止运行,运行完毕如下
1、对主进程来说,运行完毕指的是主进程代码运行完毕(主进程内的所有线程都运行完毕),和子进程无关,终止运行则需要等待子进程也运行完毕,资源回收后才会终止运行;
2、对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕(主线程运行完毕也是主进程运行完毕),但主进程结束需要等到所有子进程的结束,资源回收后才结束(主进程结束也伴随着子进程结束)
1 from threading import Thread 2 import time 3 def sayhi(name): 4 time.sleep(2) 5 print('%s say hello' %name) 6 7 if __name__ == '__main__': 8 t=Thread(target=sayhi,args=('egon',)) 9 t.setDaemon(True) #必须在t.start()之前设置 10 t.start() 11 12 print('主线程') 13 print(t.is_alive())
主线程 True
GIL全局解释锁
1 from threading import Thread 2 import time 3 4 n = 100 5 6 def task(): 7 global n 8 temp = n 9 time.sleep(0.1) 10 n = temp - 1 11 12 if __name__ == '__main__': 13 t_l = [] 14 for i in range(100): 15 t = Thread(target=task) 16 t_l.append(t) 17 t.start() 18 for t in t_l: 19 t.join() 20 print('主',n)
主 99
结果n并不是0,而是99,因为在n减1之前,99个线程都开始了,拿到的n的值都是100,因此当所有线程计算结束时n仍然是99。
加锁:
1 from threading import Thread,Lock 2 import os,time 3 def work(): 4 global n 5 lock.acquire() 6 temp=n 7 time.sleep(0.1) 8 n=temp-1 9 lock.release() 10 if __name__ == '__main__': 11 lock=Lock() 12 n=100 13 l=[] 14 for i in range(100): 15 p=Thread(target=work) 16 l.append(p) 17 p.start() 18 for p in l: 19 p.join() 20 21 print(n)
死锁与递归锁
一 死锁现象
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁。
1 class MyThread(Thread): 2 def run(self): 3 self.func1() 4 self.func2() 5 def func1(self): 6 mutexA.acquire() 7 print('