文章目录
1. 进程与线程
1.1 进程
程序的一次执行,程序装入内存,系统分配资源运行。每个进程有自己内存空间、数据栈等,只能使用进程间的通信,而不能直接共享信息。
1.2 线程
所有线程运行在同一个进程中,共享相同的运行环境。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。线程的运行可以被抢占(中断)或者被暂时挂起(睡眠),让其它线程运行(让步)。一个进程中的各个线程间共享同一片数据空间。
2. Thread
2.1 顺序执行单线程
from threading import Thread
def count():
for i in range(1000):
pass
def main():
pass
import time
start_time = time.time()
for tid in range(2):
t = Thread(target=count)
t.start()
t.join() # 阻塞线程,直到在执行的线程执行完毕,才继续执行下一个线程,实现两个顺序执行的单线程
end_time = time.time()
print('需要{}s时间'.format(end_time - start_time))
if __name__ == '__main__':
main()
执行记录:需要0.0017230510711669922s时间
2.2 同时执行两个并发线程
from threading import Thread
def count():
for i in range(1000):
pass
def main():
import time
thread_set = {}
start_time = time.time()
for tid in range(2):
t = Thread(target=count)
t.start()
thread_set[tid] = t
for i in range(0):
# 分别阻塞两个线程直到执行完毕
thread_set[i].join()
end_time = time.time()
print('需要{}s时间'.format(end_time - start_time))
if __name__ == '__main__':
main()
执行记录:
需要0.0017502307891845703s时间
结论
:对与Python来说,多线程在提高执行效率还是有压力的,甚至没有单线程的执行效率高。
3. GIL全称全局解释器锁
3.1 GIL概述
GIL全称全局解释器锁(Global Interpreter Lock),GIL并不是Python的特性,它是实现Python解释器(CPython)时引入的概念。GIL是一把全局排他锁,同一时刻只有一个线程在运行。
3.2 GIL的缺点
GIL的存在会对多线程的效率有很大影响,甚至就几乎等于Python就是一个单线程的程序。
3.3 multiprocessing库
multiprocessing库的出现解决很大程度上是为了弥补thread库(GIL低效的缺陷)。它完整地复制一套thread所提供的接口方便迁移。唯一的不同是t它使用多进程而不是多线程。每个进程有自己独立的GIL,因此也不会出现进程之间的GIL争抢。
4. Python多进程
4.1 fork操作
fork是调用一次,返回一次。因为操作系统自动把当前进程(称为父进程)复制一份(称为子进程),然后分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。子进程只需要调用getppid()就可以拿到父进程的ID。下面使用程序来执行:
import os
print('当前进程的ID是:%s'%os.getpid())
pid = os.fork()
if pid == 0:
print('我是子进程:%s,我的父进程是:%s'%(os.getpid(),os.getppid()))
else:
print('我是父进程:%s,我创建了一个子进程:%s'%(os.getpid(),pid))
执行记录:
当前进程的ID是:27409
我是父进程:27409,我创建了一个子进程:27410
我是子进程:27410,我的父进程是:27409
4.2 Process类
multiprocessing是跨平台版本的多进程库,提供一个Process类来代表一个进程对象。如果我们不使用多进程,示例程序如下:
import time
def foo(n):
time.sleep(1)
print(n*n)
if __name__ == '__main__':
start_time = time.time()
for i in range(10):
foo(i)
end_time = time.time()
print('花费时间:%s'%(end_time - start_time))
执行记录:
0
1
4
9
16
25
36
49
64
81
花费时间:10.024896144866943
下面是Process类的使用示例:
from multiprocessing import Process
import time
def foo(n):
time.sleep(1)
print(n*n)
if __name__ == '__main__':
start_time = time.time()
for i in range(10):
p = Process(target=foo,args=[i,])
p.start()
end_time = time.time()
print('花费时间:%s'%(end_time - start_time))
执行记录:
花费时间:0.12117958068847656
0
4
1
16
25
9
36
81
64
49
结论:可以看到如果使用单进程来写则需要执行10s以上的时间,而使用多进程则启动10个进程并行执行只需要不到不到1s的时间。
4.3 进程间的通信
如果用线程不需要定义队列,因为多个线程共同操作同一块数据和内存空间,但是进程之间的资源是隔离的,所以需要建立队列进行数据之间的传递。Queue是多进程安全队列,可以使用Queue实现多进程之间的数据的传递。
from multiprocessing import Process,Queue
def write(q):
for item in ['A','B','C','D']:
# 把数据读到队列中
q.put(item)
# 打印读到队列中的数据
print('Put %s to queue!'%item)
# 读入队列后停0.5再读
time.sleep(0.5)
def read(q):
while True:
print('get {} from queue'.format(q.get(True)))
if __name__ == '__main__':
# 初始化队列
q = Queue()
# 开进程把数据读到队列中
pw = Process(target=write,args=[q,])
# 开进程把数据从队列中读出来
pr = Process(target=read,args=[q,])
# 开始“写”的进程
pw.start()
# 开始“读”的进程
pr.start()
# 阻塞进程,等待“读”的进程执行完后再继续执行其它的
pr.join()
# 关闭读的进程,防止死锁,将读的进程强制退出
pr.terminate()
执行记录:
Put A to queue!
get A from queue
Put B to queue!
get B from queue
Put C to queue!
get C from queue
Put D to queue!
get D from queue
4.4 进程池Pool
进程池Pool用于批量创建子进程,可以灵活控制子进程的数量, 下面是进程池的例子:
import time
from multiprocessing import Pool
def foo(x):
print(x*x)
time.sleep(2)
return x*x
if __name__ == '__main__':
# 设置启动进程的数量
pool = Pool(processes=5)
ret_list = []
for i in range(10):
# 这是以异步的方式启动进程,如果想以同步的方式启动进程,可以使用apply方法,也可以在每次启动进程后调用res.get()方法
ret = pool.apply_async(foo,[i,])
print('-------:',i)
ret_list.append(ret)
pool.close()
pool.join()
for item in ret_list:
print('result:',item.get())
执行记录:
0
1
4
9
16
-------: 0
-------: 1
-------: 2
-------: 3
-------: 4
-------: 5
-------: 6
-------: 7
-------: 8
-------: 9
25
36
49
64
81
result: 0
result: 1
result: 4
result: 9
result: 16
result: 25
result: 36
result: 49
result: 64
result: 81
4.5 多进程与多线程
一般情况下多个进程的内存资源是独立的,而多线程可以共享同一个进程中的内存资源。下面是一个多进程与多线程区别的例子:
from multiprocessing import Process
import threading
import time
lock = threading.Lock()
def run(data_list,i):
lock.acquire()
data_list.append(i)
lock.release()
print('%s\n'%data_list)
if __name__ == '__main__':
data_list = []
for i in range(10):
# target为每个子进程执行函数,args给函数传递参数
p = Process(target=run,args=[data_list,i])
# 进程开始执行
p.start()
# 进程执行完后,执行线程的情况。为方便看结果,等待1s后执行线程
print(data_list)
time.sleep(1)
print('--------线程执行--------')
for i in range(10):
t = threading.Thread(target=run,args=[data_list,i])
t.start()
执行记录:
[0]
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
--------线程执行--------
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7]
5. 函数式编程
函数式编程的三大特性是:不可变数据、函数像变量一样的使用、尾递归优化(每次递归都重用stack,递归的深度非常深,可能导致堆栈溢出。但是,函数式编程每次递归都会用重用stack。Python是使用一个stack,还是不支持的)。优点是:很容易并行运行、惰性求值、函数返回的结果是确定性。
5.1 lambda
lambda快速定义单行的最小函数,是行内的匿名函数。下面是lambda的一个小例子:
# 第一种使用方式
f = lambda x:x*2
print(f(10))
# 第二种使用方式
print((lambda x:x*2)(10))
执行记录:
20
20
5.2 map
map(function,sequence):对序列sequence中的每一个元素item依次执行function(item),执行结果组成一个list返回。下面是几个例子:
例子1:计算列表中字符串元素的个数
# 没有使用map
for i in ['1','12','123']:
print(len(i))
# 使用map
print(map(len,['1','12','123'])) # <map object at 0x7fe498b7fc10>
print(list(map(len,['1','12','123']))) # [1, 2, 3]
例子2:使用map将列表中字符串元素转换成大写
# 没有使用map
def upper(item):
return item.upper()
lst = ['thanlon','kiku']
for item in lst:
print(upper(item))
# 执行记录:
'''
THANLON
KIKU
'''
# 使用map
lst = ['thanlon','kiku']
print(list(map(lambda item:item.upper(),lst)))
# 执行记录
'''
['THANLON', 'KIKU']
'''
例子3:使用map对列表中的每个元素求平方
# 不使用map
def foo(item):
return item**2
lst = [1,2,3,4,5,6]
lst2 = []
for item in lst:
lst2.append(foo(item))
print(lst2)
# 执行记录
'''
[1, 4, 9, 16, 25, 36]
'''
# 使用map
lst = [1,2,3,4,5,6]
print(list(map(lambda item:item**2,lst)))
# 执行记录
'''
[1, 4, 9, 16, 25, 36]
'''
5.3 filter
filter(function,sequence):对序列sequence中的item依次执行function(item),将执行的结果为True的item组成一个list/string/tuple(取决于序列sequence的数据类型)返回。下面是一个filter的使用例子:
number_list = list(range(-5,5))
print(number_list)
ret_list = list(filter(lambda x:x>0,number_list))
print(ret_list)
执行记录:
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
[1, 2, 3, 4]
5.4 reduce
reduce(function,sequence,starting_value):对序列中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用。下面写了一个reduce的例子:
# 注意:reduce在Python3中已经不是内置函数了,这里需要导入这个函数
from functools import reduce
print(reduce(lambda x,y:x*y,range(1,4)))
print(reduce(lambda x,y:x*y,range(1,4),10))
执行记录:
6
60
5.5 函数式编程总结
通过一个小例子来总结这部分内容,问题是:计算列表中正数和的平均数,下面先给出一般的写法:
num = [2,-5,9,7,-2,5,3,1,0,-3,8]
sum = 0
count = 0
for item in num:
if item>0:
sum += item
count += 1
ave = sum/count # 得到的不是一个整数
print(ave)
使用函数式编程来做:
from functools import reduce
num = [2,-5,9,7,-2,5,3,1,0,-3,8]
lst = list(filter(lambda x:x>0,num))
ave = reduce(lambda x,y:x+y,lst)/len(lst)
print(ave)
执行记录:
5.0
6. Hadoop
Hadoop是Apache开源组织的一个分布式计算开源框架,核心的设计是MapReduce和HDFS(Distributed File System),下面是我到网上找的Hadoop的分布式架构图: