一、线程理论
1、什么是线程
线程指的是一条流水线的工作过程
进程根本就不是一个执行单位,进程其实是一个资源单位--------将资源集合到一起:
一个进程内自带一个线程,线程才是CPU上的执行单位
2、进程VS线程
1、同一进程内的线程们共享该进程内资源,不同进程内的线程资源肯定是隔离的
2、创建线程的开销比创建进程要小的多(大概是100倍)
把操作系统比做一座工厂进行比对:
工厂=====》车间=====》流水线
操作系统====》进程=====》线程
开启一个进程,就是申请了一个内存空间,将产生的数据丢掉里面,而代码的运行就是线程
多个进程内存空间彼此是隔离的,而同一个进程下的多个线程,共享该进程内的数据,分散于不同进程之间的线程数据是隔离的
3、创建进程的开销远大于开启线程的开销:
造进程要向操作系统发请求,由操作系统帮你申请一个内存空间,
在一个进程再开启一个请求,不需要在申请内存空间,只需要告诉操作系统只需要执行代码即可
创建进程的开销要远大于线程
形象比喻:
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小
进程之间是竞争关系,线程之间是协作关系
形象比喻:
车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)
二、开启线程的两种方式
方式一、
直接调用线程类,指定开启的子进程函数
#开启线程的方式一、 # 常用方式: from multiprocessing import Process from threading import Thread import time def task(name): print('%s is running' %name) #最先被打印出来 time.sleep(3) print('%s is done' % name) #最后被打印出来 if __name__ == '__main__': #线程可以不用__main__,但是开启进程必须用,因为开启进程为从新在导入模块 # t=Thread(target=task,args=('egon',)) t=Process(target=task,args=('egon',)) t.start() #开启线程的速速很快,几乎向操作系统发起请求就被开启(不需要申请内存空间);而开启进程要向操作系统发起请求,申请内存空间,而这个时间已经够运行到下一行代码的了 print('主线程') #开启线程打印结果: ''' egon is running 主线程 egon is done ''' #开启进程打印结果: ''' 主线程 egon is running egon is done '''
方式二、
自定义一个线程类,并继承Thread类,然后调用自定义类
#自定义一个类、但是还要用被人内置的方法名 from multiprocessing import Process from threading import Thread import time class MyThread(Thread): def run(self): print('%s is running' %self.name) time.sleep(3) if __name__ == '__main__': t=MyThread() t.start() print('主线程')
三、进程与线程对比
#2、线程创建开销小-----因为其不需要申请内存空间 # 线程的开启速度更快 from threading import Thread from multiprocessing import Process import os def work(): print('hello') if __name__ == '__main__': #在主进程下开启线程 t=Thread(target=work) t.start() print('主线程/主进程') ''' 打印结果: hello #子进程内的打印先被打印出来,因为子进程开启不需要申请内存空间,所以开启速度非常快 主线程/主进程 ''' #在主进程下开启子进程 t=Process(target=work) t.start() print('主线程/主进程') ''' 打印结果: 主线程/主进程 #由于子进程的开启需要申请内存空间,需要时间,而代码的运行速度是很快的,所以'主线程/主进程' 会先与子进程的代码被打印出来 hello '''
from threading import Thread import time,os import time x=100 def task(): global x x=0 #子线程内的修改,可以站在主线程的角度去看,确实变量的值也被修改了 if __name__ == '__main__': t=Thread(target=task,) t.start() # time.sleep(3) #模拟开启线程和线程进行修改数据的时间 t.join() #主线程等着子线程运行完毕 print('主线程',x)
'''疑问线程有没有pid? 线程有没有杀死的需求''' from threading import Thread from multiprocessing import Process import time,os '''同一个进程内,主线程和子线程的PID是一样的''' def task(): print('%s is running' %os.getpid()) time.sleep(3) if __name__ == '__main__': t=Thread(target=task,) t.start() print('主线程',os.getpid()) '''开启多个子进程每个子进程都有不同的PID''' p1=Process(target=task) p2=Process(target=task) p1.start() p2.start() print('主线程、主进程',os.getpid())
四、线程对象的其他方法
1、Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
2、threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
3、线程对象其他方法的应用
from threading import Thread,current_thread,active_count,enumerate import time,os def task(): print('%s is running' %current_thread().name) #在当前线程查看自己的线程名 time.sleep(3) print('%s is done' % current_thread().getName()) #在当前线程查看自己的线程名current_thread().name等价于current_thread().getName() if __name__ == '__main__': t1=Thread(target=task,name='第一个线程') # t2=Thread(target=task,) # t3=Thread(target=task,) t1.start() # t2.start() # t3.start() # print(t1.is_alive()) #判断线程是否存活 print(active_count()) #活跃的线程个数 print(enumerate()) #把当前活跃的线程对象放到列表中,可以通过点来使用线程的属性 current_thread().setName('修改后的主线程名') print('主线程',current_thread().name) #查看主线程的名字,也可以修改主线程的名字
五、守护线程
守护线程会在什么时候死?
守护线程会等到该进程内所有的非守护线程都结束才跟着结束
守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)
强调:
守护进程是父进程结束,守护的进程就随之结束
from threading import Thread,current_thread import time def task(): print('%s is running' %current_thread().name) time.sleep(3) print('%s is done' % current_thread().name) #守护的线程已经结束,所以看不到 if __name__ == '__main__': t1=Thread(target=task,name='第一个线程') t1.daemon = True t1.start() #由于开的是线程的速度太开了,所以 print('%s is running' %current_thread().name)会被先打印出来 print('主线程') ''' 打印结果: 第一个线程 is running 主线程 '''
守护进程要等所有的非守护进程结束才会结束,而不是主线程结束守护进程就结束
from threading import Thread import time def foo(): print(123) time.sleep(5) print("end123") #不可能看到,原因是所有的非守护线程都已经运行完毕,自己也就随之结束,只有当睡的时间比非守护线程短才能看到 def bar(): print(456) time.sleep(3) print("end456") if __name__ == '__main__': t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------") ''' 123 456 main------- end456 '''
六、线程互斥锁
同一时间只有一个人能拿到锁
from threading import Thread,Lock import time mutex=Lock() x=100 def task(): global x mutex.acquire() #大家都声明了全局变量,加锁让他们等,同一时间只有一个人拿到 temp=x #保证100个线程都拿到了x,即都拿到了初始值 time.sleep(0.1) #都睡了0.1秒,这个时间已经都后面99个线程起来,那么他们谁先谁后抢到锁就不一定了 x=temp-1 #正常已改改为0 mutex.release() if __name__ == '__main__': start=time.time() t_l=[] for i in range(100): #用for循环是有先后顺序,真实可能是同时起来的 t=Thread(target=task) t_l.append(t) t.start() #速度太快了,几乎是start同时 for t in t_l: t.join() print('主',x) print(time.time()-start)
七、死锁现象及解决方法递归锁
死锁现象:
死锁现象是由于锁套锁导致的,两个线程互相拿到对方的锁就都被锁死了
'''死锁现象''' from threading import Thread,Lock,RLock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('%s 拿到了A锁' %self.name) mutexB.acquire() print('%s 拿到了B锁' %self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print('%s 拿到了B锁' %self.name) time.sleep(0.1) mutexA.acquire() print('%s 拿到了A锁' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start() ''' Thread-1 拿到了A锁 #线程1首先抢到了A锁,还没有释放,其他人只有等线程1把A锁释放,才能抢到A锁 Thread-1 拿到了B锁 #线程1又抢到了B锁,然后释放掉了B锁和A锁,那么其他人此时就可以争抢A锁, Thread-1 拿到了B锁 #线程1紧接着又拿到了B锁,睡了一会准备去抢A锁,但是此时A锁已经被被人抢走,所以就会在原地阻塞住 Thread-2 拿到了A锁 #线程1释放的A锁被线程2抢到,然后线程2又想去抢A锁,但是此时B锁已经被线程1抢走,所以线程1就被阻塞住 #这就导致线程1和线程2互相锁死的现象,我们采用递归锁来进行解决死锁现象
解决死锁现象
from threading import Thread,Lock,RLock import time mutexA=Lock() mutexB=Lock() mutexA=mutexB=RLock() #RLock递归锁来解决死锁现象,递归锁的特点是可以连续的acquire class MyThread(Thread): def run(self): self.f1() self.f2() def f1(self): mutexA.acquire() print('%s 拿到了A锁' %self.name) mutexB.acquire() print('%s 拿到了B锁' %self.name) mutexB.release() mutexA.release() def f2(self): mutexB.acquire() print('%s 拿到了B锁' %self.name) time.sleep(0.1) mutexA.acquire() print('%s 拿到了A锁' %self.name) mutexA.release() mutexB.release() if __name__ == '__main__': for i in range(10): t=MyThread() t.start() t1=MyThread() t1.start() t2=MyThread() t2.start() t3=MyThread() t3.start() print('主')
八、信号量
# from multiprocessing import Semaphore # 信号量也是一种锁 from threading import Thread,Semaphore,current_thread import time,random # 同一时间并发执行的任务数 # 而互斥锁同一时间只有一个正在运行 sm=Semaphore(5) #得到一个信号量的对象 def go_wc(): sm.acquire() print('%s 上厕所ing' %current_thread().getName()) time.sleep(random.randint(1,3)) sm.release() if __name__ == '__main__': for i in range(23): t=Thread(target=go_wc) t.start()