1.进程与线程
1.1 进程与线程的理解,它们都是操作系统上的基本概念
进程:(来个不太懂的解释) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。说的简单点就是你启动个软件,就启动了一个进程(虽然不严谨,但可以这样认为)
线程:(来个官方的) 有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。(比如说,你打开个word,这时你就相当于打开了一个进程,但是,你在输入的时候,word还会做些别的事,如拼写检查,转化等,这时候就需要线程出来了,这也就是说线程是程序执行流的最小单元了)
区别
-进程间相互独立,同一进程中的各个线程间共享,某个进程内的线程在其它里程中不可见
-进程间通信需要IPC(如管道,信号,消息队列,共享内存,信号量,套接字(socket)等
),而线程间可以直接读写进程数据段(如全局变量)来进行通信
-线程的上下文切换比进程的上下文切换要快的多
-在多线程os中,进程不是一个可执行的实体
1.2 多任务的实现方式有3种
-
多进程模式
-
多线程模式
-
多进程+多线程模式
python 是支持多进程,又支持多线程的,php在这方面就有点力不从心了(php 本身是单进程的,只不过由php-fpm同进启动多个php进程,才能有并发请求)
来补充一下,php-fpm,fastcgi,cgi的三个搞基的关系吧,cgi 全称(COMMON GATEWAY INTERFACE),公共网接口,说白了就是一种规范与标准,当nginx服务器接收到浏览器传过来的数据后,如果访问的是动态页面请求,nginx需要与php通信了,这时候cgi协议上场了,nginx 需要根据这个协议,把前端传过来的数据,转换成php解析器能理解的信息,等php解析好,获取到数据后,也需要根据这个协议转化成nginx可以理解的信息,让nginx把数据返回给浏览器.
cgi这个货呢,有个缺点,当每次链接请求时,都会重新启动进程,处理信息,再关闭进程,这样这个效率是不是有点低? 这个时候fastcgi由一个外国屌丝发明成功了,它的原理就是启动后,会有一个类似于master的进程去管理,当有请求进来时,会创建一个worker进程,去处理,这样呢就减少了进程开启与关闭的过程,效率更高点
php-fpm这货的出现主要是,你像这个cgi,fastcgi这些进程,只能处理请求啥的,别的事就会做了如,重启,平滑重启啥的,php-fpm就是相当于管理它们的一种服务(可以这么理解吧)
这里有点要注意:在php-fpm配置文件里有max_request (最大请求数),这里的请求数并不是客户请求最大数量,而是指一个php-fpm的worker进程在处理多少个请求后就终止,不理解?,来说一下,php在处理完请求后,会释放空间,释放变量等所以不会出现内存溢出的情况,但是php-fpm并不会把释放的空间还给内存,所以需要设置一个请求数,当达到这个请求数,这个进程就停止,关闭,释放内存,再重新开启一个进程
unix/linux
操作系统提供了一个fork()
系统调用,它调用一次,会把当前进程(父进程)复制一份(子进程),会返回两次,一次在父进程里返回,一次在子进程时返回,子进程永远返回0,而父进程返回子进程id,这样做的理由是,一个父进程可以fork出很多个子进程,所以父进程要记下每个子进程的id,而子进程只需要调用getppid()
就可以拿到父里程id
2. python中的多进程
2.1
python中的模块os
封装了常见的系统调用
import os
print('process %s start...' % os.getpid()) # getpid() 获取当前进程id
pid=os.fork()
if pid==0: # 如果pid=0 说明是在子进程里返回的
print('我是子进程 %s,的我父进程是%s',os.getpid(),os.getppid())
else:
print('父进程%s,我创建了子进程%s',os.getpid(),pid)
由于windows下没有fork()
,python中提供了multiprocessing
模块,来支持多进程开发操作,来模拟出类似于fork()
from multiprocessing import Process\
import os
#子进程要执行的代码
def run_proc(name):
print('run child process %s (%s)..' % (name,os.getpid()))
if __name__=='__main__':
print( 'parent process %s.' % os.getpid())
p=Process(tartget=run_proc,args=('test',))
print('clild process will start')
p.start()
p.join()
print('child process end')
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例,用start()
方法启动
join()
方法可以等待子进程结束后再继续往下运行,通常用于里程间的同步
2.2 使用进程池批量创建子进程(Pool)
form mutiprocessing import Pool
import os,time,random
def long_time_task(name):
print('run task %s (%s)...'%(name,os.getpid()))
start=time.time()
time.sleep(random.random()*3)
end=time.time()
print('task %s runs %0.2f seconds' % (name,(end-start)))
if __name__='__main__':
print('parent process %s' % os.getpid())
p=Pool(4)
for i in range(5):
p.apply_async(long_time_task,args=(i,))
print('waiting for all subprocesses done ...')
p.close()
p.join()
print('All subprocesses done.')
2.2 子进程
subprocess
模块可以让我们非常方便的启动一个子进程,然后控制输入与输出
下面的例子演示了如何在Python代码中运行命令nslookup www.python.org
,这和命令行直接运行的效果是一样的:
import subprocess
print('$ nslookup www.python.org')
r=subprocess.call(['nslookup','www.python.org'])
print('code',r)
2.3 进程间通信
进程间需要通信,python的multiprocessing
模块包装了底层的机制,提供了queue
,Pipes
等多种方式来交换数据,以queue
为例,创建两个子进程,一个进程向queue
写数据,一个子进程向queue
读数据
from multiprocessing import Process,Queue
import os,time,random
#写数据进程执行的代码
def write(q):
print('print to write %s ' % os.getpid())
for value in ['a','b','c']:
print('Put %s to queue ..' % value)
q.put(value)
time.sleep(random.random())
#读数据进程执行的代码
def read(q):
print('process to read %s' % os.getpid())
while True:
value=q.get(True)
print('get %s from queue' % value)
if __name__='__main__':
#父进程创建queue,并给各个子进程
q=Queue()
pw=Process(target=write,args=(q,))
pr=Process(target=read,args=(q,))
#启动子进程pw,写入
pw.start()
#启动子进程,读取
pr.start()
#等待pw结束
pw.join()
# pr进程是死循环,无法等待结果,只终止
pr.terminate()
3. 多线程的实现
3.1 threading
多线程模型
python 提供两个模块_thread
和threading
,threading
是对_thread
模块的封装,启动一个线程,就把一个函数传入代创建Thread
实例,然后调用start()
开始执行
import time, threading
#新线程执行的代码
def loop():
print('thread %s is runing'% threading.current_thread().name)
n=0
while n<5:
n=n+1
print('thread %s >>>> %s' %(threading.current_thread().name,n))
time.sleep(1)
print('thread %s ended ' % threading.current_thread().name)
t=threading.Thread(target=loop,name='loopThread')
t.start()
t.join()
print('thread %s ended' % threading.current_thread().name)
任何一个进程都会默认启动一个线程,我们称为这个线程为主线程,主线程又可以启动新线程,由于多线程是对变量共享的,所以说会出现锁的机制,才会保证数据的一致性(因为cpu的工作原理,相当于线程的上下文切换,如果有两个线程,它会用线程1处理一行,有可以再用线程2处理下一行这种情况,是系统自己调度的,所以会导致数据有可能不一致,才会有锁的制出现)
一个进程中只有一把锁,无论多少个线程,要想保证数据一致,需要先获取锁,再执行操作,最后再释放锁
n=0
lock=threading.Lock() #声明一把锁
def run_thread(n):
for i in range(100000):
#先获取锁
lock.acquire()
try:
#处理数据信息
pass
finally:
#修改完成一定要释放锁
lock.release()
当多个线程同时执行lock.acquire()
时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally
来确保锁一定会被释放。
缺点:多线程的并发性能就不好了,只能等待锁释放才能处理,python好像不支持多核,咱也不敢说,咱也不敢问
3.2 ThreadLocal
模块 是解决线程中变量共享的模块
在多线程里,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,但是也出现了在函数中传递变量时,传递越来比较麻烦
ThreadLocal
模块的原理就是在线程内定义一个全局变量,使用当前thread
自身作为key
来进行操作
import threading
#创建全局的threadLocal 对象
local_school=threading.local()
def process_student():
#获取当前线程关联的student
std=local_school.student
print('hello %s (in %s)' % (std,threading.current_thread().name))
def process_thread(name):
local_school.student=name
process_student()
t1=threading.Thread(target=process_thread,args=('alice'),name='thread-1')
t2=threading.Thread(target=process_thread,args=('alice),name='thread-2')
t1.start()
t2.start()
t1.join()
t2.join()
全局变量local_school
就是一个ThreadLocal
对象,每个Thread
对它都可以读写student
属性,但互不影响。你可以把local_school
看成全局变量,但每个属性如local_school.student
都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal
内部会处理。
可以理解为全局变量local_school
是一个dict
,不但可以用local_school.student
,还可以绑定其他变量,如local_school.teacher
等等。
ThreadLocal
最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。