最近做项目的时候因为要对大批量的数据进行处理,所以立马想到的是用多线程/多进程进行加速处理,但是python中多线程和多进程还是有一些区别,因此使用的场景也有所不同。
多线程与多进程的区别
记得教科书上一句经典的对多线程和多进程的介绍:”进程是资源分配的最小单位,线程是cpu调度的最小单位“。简单说来就是,多线程共享内存,内存占用少,cpu调度简单,cpu利用率高;多进程每个进程独享内存,内存占用高,cpu调度复杂,cpu利用率低。了解了这些,如果是使用过c++或者java的程序员会很好的根据场景选择多线程/多进程,但是在python中如果也按照这种规律去选择就会有一些问题。
python多线程
python是一种解释性语言,为了安全的考虑就设计了一个GIL(Global Interpreter Lock,全局解释器锁)的东东,每个进程一个GIL,如果进程下面有多个线程,则线程竞争获取锁进行执行,那么就意味着多线程并不是并行的,每次只有一个线程在执行,也就是很多人说的python多线程是假的。
那么就会有人有疑问了,既然是假的还实现它干嘛?因为有一种任务,如果使用的cpu比较少,很多时候都是在等待做其他的事情的情况下,就可以使用多线程进行加速,例如:IO操作/网络的请求操作等。很典型的一个例子就是爬虫,很多时候爬虫都在进行http的请求,请求一般都是很耗时的,还经常会面临请求超时失败的情况,这个时候线程会让出cpu给其他任务,起到合理利用cpu的目的。
场景一:pywsgi多进程
默认情况下,pywsgi是单进程的,在请求量大的情况下会进行阻塞,如果部署的服务器是多核的,可以,开启多进程可以加大并发请求量,充分利用多核的算力:
def serve_forever(server):
server.start_accepting()
server._stop_event.wait()
app = Flask(__name__, static_folder='app', static_url_path='')
server = pywsgi.WSGIServer(('0.0.0.0', ASK_SERVICE_PORT), app)
server.start()
for i in range(cpu_count() * 2 + 1):
Process(target=serve_forever, args=(server,)).start()
进程数为cpu数*2+1。
场景二:api接口异步执行
在工程中,有些api请求的执行时间很久,如果等待执行完再返回结果就会阻塞调用方,这时可以将接口做成异步的,接收到的参数后加入到队列或者redis里面,开启多线程或者多进程去执行后面的任务。
例如:提交资料接口,收到请求后将数据放到redis里面,就返回结果。
def submit_material_by_url(self, url, tree_name, node_id):
data = {
"url": url,
"tree_name": tree_name,
"node_id": node_id
}
self.data_redis.put_url(json.dumps(data))
return {'err': ErrorCode.SUCCESS}
开启多线程启动下载任务。
# 根据url下载资料处理进程
self.process = Thread(target=self._process, name="process_submit_material_by_url")
self.process.start()
def _process(self):
while True:
data = self.data_redis.get_url()
try:
if data is None:
continue
data_dict = json.loads(data)
url = data_dict["url"]
tree_name = data_dict["tree_name"]
node_id = data_dict["node_id"]
self.query_skill_tree_view.submit_material_by_url(url, tree_name, node_id)
except Exception as e:
logger.error(traceback.format_exc())
if data is not None:
self.data_redis.put_url(data)
time.sleep(2)
场景三:多进行分词
在对大批量的数据进行分词的时候,如果使用单进程进行处理,速度将会非常慢,往往要处理很久。这个时候可以使用多进行进行加速,分词主要使用cpu。
这里使用线程池的map操作,对分词后的结果写文件。
def _cut_data(self, processes=4):
"""对语料进行分词"""
new_data = []
pool = Pool(processes)
data_list = []
with open(self._blog_data_selected_path) as file:
for line in file:
data_list.append(line.strip().lower())
new_data = pool.map(self._simple_cut, data_list)
with open(self._blog_data_selected_seg_path, "w") as file:
file.writelines([line + "\n" for line in new_data])
return True