Python分布式计算

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的分布式架构图:
在这里插入图片描述

发布了54 篇原创文章 · 获赞 138 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Thanlon/article/details/103025808