falsk开发Python并行编程

并行编程概念

一、线程和进程

1 什么是进程

对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

2 什么是线程

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

3 什么是多任务?

现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。

什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

二.并行编程概念

通俗一点,
串行 : 你在家做功课,功课做完给家长检查签字,接着你才可以看电视玩游戏,前提是你必须做完一件事才可以做下一件。

并行:你可以一边做功课,一边看着电视,玩着游戏,这些任务都可以同时进行着,你不需要等待一件事情的结果才可以继续向下进行。

大部分时候我们编写的程序和服务都是以串行来运行的,也就是单进程,单线程的模式和思想来写的,但是大部分的实际生产过程中,多个任务如何同时处理的问题就来了,单进程和单线程远远达不到我们的需求,这时候我们就引用了多线程多进程的概念,也就是多任务的概念。

下面是生产过程中实例

三. flask 使用多线程实例

1 falsk 启动代码
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
2 flask启动多线程
from threading import Thread
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

def task1():
    return 'Hello World1!'

def task2():
    return 'Hello World2!'

class MyThread(Thread):
    def __init__(self, func, args):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args

    def run(self):
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception:
            return None
	
if __name__ == "__main__":
    t1 = MyThread(task1, args=())
    t2 = MyThread(task2, args=())

    t1.start()
    t2.start()
	app.run()

当然在这里我们只是单纯的在主服务启动过程中去调用了一个函数task1和task2函数,当然不满足需求,这个例子是想表达,在主程序启动之前你可以开启多个线程去启动别的服务。

为了更加直观的理解并行编程,接下来我们用socket为业务需求进行改造,

3 flask并行编程

*假定业务流程是这样,
1 任务函数hello_world将他的任务信息放入某一个管道中(管道的概念可以暂时用全局变量方法去展现)

2 将task1,task2两个任务函数做到时时监控处理也就是我们提到的并行开发。

3 task1 是一个处理任务信息的功能函数,比如后台有新的消息时候,我们要给所有的用户推送这一个消息,并且要给task2函数任务处理完成的状态的ack信息。

4 task2 主要来处理任务是否成功,将任务的状态写入日志,或者别的操作。

from threading import Thread
from flask import Flask
app = Flask(__name__)


task_information = [] # 新建task_information列表用来作为任务信息存放的列表
task_ack = []  # 新建task_ack列表用来作为任务确认的列表

@app.route('/')
def hello_world():

    global task_information   #  设置全局变量是为了task1和task2都可以共同去使用它。
    global task_ack
    task_information.append('data')  # 主要的程序接受到任务后,将它需要处理的信息放入任务信息管道当中
	return 'Hello World!'


def task1():
	while 1:
		if task_information: # 这个位置处理循环的处理task_information里面的信息
			print(task_information) # 这里的打印换成实际的工作,更新数据库,或者别的什么处理
			task_information.clear() # 处理完成后,清除任务信息的全局变量。
			task_ack.append(1)
		else:
			time.sleep(5)
    return 'Hello World1!'

def task2():
	while 1:
		if task_ack:  # 如果处理任务后的数据,确认之后,我们继续下一步操作
			print(task_ack) # 这里的打印换成实际的工作,写入日志,或者别的什么处理
			task_ack.clear() # 处理结果成功,清除任务确认的全局变量。
		else:
			time.sleep(5)	
    return 'Hello World2!'

class MyThread(Thread):
    def __init__(self, func, args):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args

    def run(self):
        self.result = self.func(*self.args)

    def get_result(self):
        try:
            return self.result
        except Exception:
            return None
	
if __name__ == "__main__":
    t1 = MyThread(task1, args=())
    t2 = MyThread(task2, args=())

    t1.start()
    t2.start()
	app.run()

总结

这样我们的python并行编程概念就有了,很多实际业务都需要这样的开发方式,这样可以处理不同步的问题,做到程序的并行编程!

管道的概念只是用全局变量来模仿,管道RabbitMQ、RocketMQ、ActiveMQ、Kafka等很多的消息中间件。
这里只是方便理解,实际如果业务需求不是很复杂,写入量不是很大的时候,python的列表可容纳长度完全可以支撑大部分业务了。

管道当然可以用一些redis作为数据服务器缓存这些数据,但是每次缓存就有一次网络请求,响应时间的会变慢,性能会变差,具体还是看我们实际业务的需求吧。

此文档是以单进程中的多线程处理的作为基础的,因为python的特性,是线程安全的,不存在互斥锁的概念,当然在uwsg或者nginx多进程处理下,全局变量将会不再适用,每个进程都有独立的内存空间,所以除非指向服务器相同的内存地址,也就是进行进程间通讯,有相同全局共享内存地址才可以这样使用。