多线程与多进程的理解
什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。
什么是进程
进程是指一个程序在给定数据集合上的一次执行过程,是系统进行资源分配和运行调用的独立单位。可以简单地理解为操作系统中正在执行的程序。也就说,每个应用程序都有一个自己的进程。
每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。
二者的区别
1.同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
2.同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的。
3.对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
4.线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
5.同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
6.创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
7.一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
8.线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。
GIL
说道多进程不得不说一下Python的GIL
在非python环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。但是在python中,无论有多少核,同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。
进程
Python 要进行多进程操作,需要用到muiltprocessing库,其中的Process类跟threading模块的Thread类很相似。所以直接看代码熟悉多进程。
from multiprocessing import Process
def run(num):
print('hello process')
for i in range(5):
print(num,i)
if __name__ =='__main__':
for i in range(5):
proces = Process(target=run,args=(i,))#创建多个进程
proces.start()#启动
proces.join() #进程池中进程执行完毕再关闭,如果注释,那么程序直接关闭
线程
Python提供两个模块进行多线程的操作,分别是thread和threading,
前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。
import threading
def run(num):
print('hello threading')
for i in range(5):
print('%s -- %d' % (threading.current_thread().name, i))#注意这里的'threading.current_thread().name'这个是获取当前正在运行的线程名字
if __name__ == '__main__':
for i in range(5):
thread = threading.Thread(target=run, args=(i,))#创建多个线程
thread .start()#启动线程
选择多进程还是多线程
CPU 密集型:程序比较偏重于计算,需要经常使用 CPU 来运算。例如科学计算的程序,机器学习的程序等。
I/O 密集型:顾名思义就是程序需要频繁进行输入输出操作。爬虫程序就是典型的 I/O 密集型程序。
如果程序是属于 CPU 密集型,建议使用多进程。而多线程就更适合应用于 I/O 密集型程序。
协程
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序。
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)。
常用第三方模块gevent和greenlet。(本质上,gevent是对greenlet的高级封装,因此一般用它就行,这是一个相当高效的模块。)
简单的协程爬虫
from urllib import request
import ssl
import gevent
from gevent import monkey
#把当前程序的所有的IO操作 单独做上标记
monkey.patch_all()
ssl._create_default_https_context = ssl._create_unverified_context
def spider(url):
print('GET:%s' %url)
resp = request.urlopen(url)
print(resp)
data = resp.read()
f = open("test.html","wb")
f.write(data)
f.close()
print("%d --------------- %s" %(len(data),url))
gevent.joinall([gevent.spawn(f,'http://www.qiushibaike.com/'),
gevent.spawn(f,'http://news.qq.com/'),
gevent.spawn(f,'http://www.toutiao.com/'),
])
协程的优点
1 : 线程在单线程下切换,减少资源消耗
2 : 无需原子操作控制流,简化编程模型
3 : 高并发,高扩展,低成本.