07 线程 与进程类似
一、什么是线程
在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程
进程只是用来把资源集中到一起,提供代码运行所需要的资源(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位
线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程
车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线
流水线的工作需要电源,电源就相当于cpu
多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间
相当于一个车间内有多条流水线,都共用一个车间的资源。
例如,北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。
若多个线程都是cpu密集型的,那么并不能获得性能上的增强(一直计算都不带停的那种)
但是如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度。
二、线程创建开销小
1.创建进程的开销要远大于线程?
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小
2.进程之间是竞争关系,线程之间是协作关系?
不同的进程直接是竞争关系,是不同的程序员写的程序运行的
车间直接是竞争/抢电源的关系,迅雷抢占其他进程的网速,360把其他进程当做病毒干死
同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动
一个车间的不同流水线式协同工作的关系,迅雷内的线程是合作关系,不会自己干自己
三、进程线程不同之处
地址空间 :线程共享创建它的进程的地址空间;进程有自己的地址空间。
数据使用:线程可以直接访问其进程的数据段,共享;进程有自己的父进程数据段副本,相互间数据不共享。
通信方式:线程可以直接与其进程的其他线程通信;进程必须使用进程间通信与兄弟进程通信。
创建方式:新线程很容易创建;新进程需要父进程的重复。
控制能力:线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
主更改对子影响:对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改不会影响子进程。
四、创建线程的两种方式
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性
#第一种 直接创建
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程,会立刻创建立刻输出 print('主') ''' egon is running #线程运行贼快,不是先输出 '主' 主 egon is over '''
#第二种 创建类方法
from threading import Thread import time class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is running'%self.name) time.sleep(3) print('%s is over'%self.name) t = MyThread('egon') t.start() print('主') ''' egon is running #线程运行贼快,不是先输出 '主' 主 egon is over '''
五、线程对象及其他方法
#os.getpid() 主线程和子线程线程号一毛一样
#t.join() 主线程等待子线程运行结束
#active_count() 当前活跃线程数
#current_thread().name 当前线程名
from threading import Thread,current_thread,active_count import time import os def task(name,i): print('%s is running'%name) # print('子current_thread:',current_thread().name) #子current_thread: Thread-1 # print('子',os.getpid()) #子 7120 这里和主线程进程号一毛一样 time.sleep(i) print('%s is over'%name) t = Thread(target=task,args=('egon',1)) t1 = Thread(target=task,args=('jason',2)) t.start() t1.start() t1.join() # 主线程等待子线程运行完毕 print('当前正在活跃的线程数',active_count()) print('主') # print('主current_thread:',current_thread().name) #主current_thread: MainThread # print('主',os.getpid()) #主 7120 ''' egon is running jason is running egon is over jason is over 当前正在活跃的线程数 1 主 '''
六、守护线程
为什么主线程运行结束之后需要等待子线程结束才能结束呢?
主线程的结束也就意味着进程的结束
主线程必须等待其他非守护线程的结束才能结束(不用管守护线程的死活)
(意味子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了进程就结束了,资源也就销毁了)
from threading import Thread,current_thread import time def task(i): print(current_thread().name) time.sleep(i) print('GG') t = Thread(target=task,args=(1,)) t.daemon = True t.start() print('主')
from threading import Thread from multiprocessing import Process import time def foo(): print(123) time.sleep(1) 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------- #主线程虽然运行完了,但是一定会等到t2结束自己才会死 end123 #主线程并没有死,自己当然也不会死 end456 '''
七、线程间通信
共享资源,直接通信
from threading import Thread money = 666 def task(): global money money = 999 t = Thread(target=task) t.start() t.join()
print(money) #999 直接就通信了
八、线程互斥锁
from threading import Thread,Lock import time n = 100 def task(mutex): global n mutex.acquire() tmp = n time.sleep(0.1) n = tmp - 1 mutex.release() t_list = [] mutex = Lock() for i in range(100): t = Thread(target=task,args=(mutex,)) t.start() t_list.append(t) for t in t_list: t.join() #让主线程等着每一个线程运行完毕再输出n print(n) #0