一、线程&进程
1、进程
在系统中,一个任务就是一个进程。比如开启浏览器,打开微信,打开两个记事本就是启动了两个记事本进程。每打开一个任务,代表在系统中启动了一个进程,进程代表着一个资源的集合
2、线程(Thread)
线程是操作系统能够运行的最小度量单位,他被包含在进程当中,是进程中实际的运行单位。
有些进程不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)
由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。线程是最小的执行单元,而进程由至少一个线程组成。
比如我们做事一个人做是比较慢的,如果多个人一起来做的话,就比较快了。程序也是一样的,我们想运行的速度快一点的话,就得使用多进程,或者多线程,在python里面,多线程被很多人诟病,为什么呢,因为Python的解释器使用了GIL的一个叫全局解释器锁,它不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。
3、进程与线程的区别
进程 : 对各种资源管理的集合
线程 : 操作系统最小的调度单位,是一串指令的集合
进程不能单独执行,它只是资源的一个集合,如果进程想要操作CPU,进程必须先创建一个线程,在进程中的所有线程,都同享同一块内存空间。进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的,进程有父进程、子进程,独立的内存空间,唯一的进程标识符【pid】(注:启动线程比启动进程快,线程的内存空间是共享的,进程的空间是独立的。)
二、多线程的使用(threading模块)
import threading import time def talk(name, age): #定义每个线程要运行的函数 print('%s说:今年我%s岁' % (name, age)) time.sleep(5) print('%s说:介绍完毕' % name) # def talk(*args, **kwargs): # print('%s说:今年我%s岁' % (args[0], kwargs['age'])) # time.sleep(2) # print('%s说:介绍完毕' % args[0]) t1 = threading.Thread(target=talk, args=('小黑','19')) #生成一个线程实例 t2 = threading.Thread(target=talk, args=('阿根',),kwargs={'age': 45}, name='线程2',group=None) # target代表你要启动的多线程运行的函数,把函数名赋值给target即可 # args代表多线程执行的函数所需要的参数, # 注:这里args如果传递多个参数时,它接收的是元组,如果是一个参数时,我们需要写逗号 # kwargs同样是多线程执行时函数所需要的参数(接受的是一个字典) # name代表这个线程的名字, # group代表线程组(Python还没有实现,所以默认必须穿None或不写) t1.start() #启动线程 t2.start() #启动另一个线程
还有一种通过继承启动多线程(两种方式没有区别只是两种写法,这种更复杂了解就好)
通过继承threading.Thread并覆盖run方法完成多线程,比我们通过def实现的多继承有局限性,我们在类中定义的run方法,不是随意定义的,函数名必须为run,否则不会重写Thread的run方法,程序不会执行多线程。
import threading import time class MyThread(threading.Thread): def __init__(self, name): super(MyThread, self).__init__() self.name = name def run(self): print(self.name, '说开始') time.sleep(2) print(self.name, '说结束') t1 = MyThread('小黑') t2 = MyThread('阿根') t1.start() t2.start()
threading与实例对象提供了几个方法:
threading.active_count() # 返回当前运行的线程个数 threading.enumerate() # 返回当前运行中的线程list threading.current_thread() # 返回当前的线程变量 t1.start() # 启动线程 t1.is_alive() # 判断线程是否在运行 运行指启动后、终止前。 t1.getName() # 获取线程名 t1.setName('填写更改后的名称') # 对线程进行命名 t1.setDaemon(True) # 设置守护线程 t1.isDaemon() # 判断是否是守护线程 t1.join(timeout=20) # 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数)
三、线程等待
多线程在运行的时候每个线程都是独立运行的,不受其他的线程干扰。如果想在哪个线程运行完之后,再做其他操作的话,就得等待它完成,使用join,等待线程结束
import threading import time def talk(name): print(name, '开始了') time.sleep(2) print(name, '结束了') t_list = [] for i in range(1, 4): t = threading.Thread(target=talk, args=('小黑%s' %i,)) t.start() t_list.append(t) start = time.time() for t in t_list: t.join() end = time.time() - start print(end) print(threading.active_count())
四、守护线程
守护线程,当主线程执行完成后,所有守护线程立即结束执行。例:一个国王(非守护线程)有很多仆人(守护线程),国王死后它的仆人也得跟着陪葬
#------守护线程------- #守护线程就是和秦始皇陪葬的人一样 #主线程就是秦始皇 #子线程就是陪葬的人。 import threading,time def run(): time.sleep(9) print('run。。。') for i in range(10): t = threading.Thread(target=run) t.setDaemon(True) #设置子线程成为一个守护线程 t.start() print('over..') #结果是:over.. #当主线程执行完后,不管子线程结没束结束都跟着主线程一起结束了 #实际场景:qq、浏览器,关闭浏览器时所有的tab页都会被关闭 #在实际写代码的过程中还没有发现有需要用守护线程的场景
五、线程锁
# 线程锁就是: # 很多线程一起在操作一个数据的时候,可能会有问题, # 就要把这个数据加个锁,同一时间只能有一个线程操作这个数据。 import threading from threading import Lock num = 0 lock = Lock() #实例化一把锁,先申请一把锁 def run(): global num # lock.acquire() #加锁 # num+=1 # lock.release() #解锁,上锁后一定要记得将锁解开,否则后面的线程无法操作同一份资源,就会导致死锁
with lock: #自动加锁解锁,python3会自动加锁,但为了保险最好还是加上锁;python2里面就需要手动加锁 num+=1 for i in range(100): t = threading.Thread(target=run) t.start() while threading.active_count()!=1: pass #判断当前活动的线程是几个,如果是1的话, #说明子线程都已经执行完成了。 print(num)
练习:通过一个简单的爬虫,看下多线程的效果
import threading import requests, time urls = { "baidu": 'http://www.baidu.com', "blog": 'http://www.nnzhp.cn', "besttest": 'http://www.besttest.cn', "taobao": "http://www.taobao.com", "jd": "http://www.jd.com", } def run(name, url): res = requests.get(url) with open(name + '.html', 'w', encoding=res.encoding) as fw: fw.write(res.text) start_time = time.time() lis = [] for url in urls: t = threading.Thread(target=run, args=(url, urls[url])) t.start() lis.append(t) for t in lis: t.join() end_time = time.time() print('run time is %s' % (end_time - start_time)) # 下面是单线程的执行时间 # start_time = time.time() # for url in urls: # run(url,urls[url]) # end_time = time.time() # print('run time is %s'%(end_time-start_time))
六、多进程(multiprocessing)
多进程要比多线程耗费的资源大,进程的数据是独立的,所以每启动一个进程都会生成一份内存来存储进程的数据。多进程实质就是启动每个进程中的默认线程去完成任务。
# Python里面的多线程,是不能利用多核CPU的, # 如果想利用多核CPU的话,就得使用多进程,python中多进程使用multiprocessing模块 from multiprocessing import Process import time def my(name): time.sleep(3) print('hello',name) p = Process(target=my,args=('hei',)) #方式与多进程相同 p.start() p.join
七、进程池
# 进程池用来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数, # 咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求; # 但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务 from multiprocessing import Pool import os def worker(msg): print("%s开始执行,进程号为%d" % (msg, os.getpid())) if __name__ == '__main__': po = Pool(3) # 定义一个进程池,最大进程数3 for i in range(0, 10): # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,)) # 每次循环将会用空闲出来的子进程去调用目标 po.apply_async(func=worker, args=(i,)) # 第一个func参数指定运行的函数,第二个args是参数,没有参数可以不写 print("----start----") po.close() # 关闭进程池,关闭后po不再接收新的请求 po.join() # 等待po中所有子进程执行完成,必须放在close语句之后 print("-----end-----")