1.多进程
1.1创建进程
Unix/Linux/Mac操作系统都可以使用fork()
函数来创建子进程,分别在父进程和子进程内返回,例如
代码:
import os print ('当前进程的ID是:%s' % os.getpid()) ID = os.fork() if ID == 0: print ('这是子进程,ID是:%s。。父进程ID是:%s' % (os.getpid(), os.getppid()))else: print ('这是父进程,ID是:%s' % os.getpid())
结果:
当前进程的ID是:1064这是父进程,ID是:1064这是子进程,ID是:1065。。父进程ID是:1064
在Windows中没有fork()
调用,可以用multiprocessing
模块的Process
类,例如
代码:
import osfrom multiprocessing import Processimport osdef run_proc(name): print('子线程 %s ,ID是:%s' % (name, os.getpid()))print('当前线程(父线程)的ID是: %s' % os.getpid())p = Process(target=run_proc, args=('test',)) p.start() p.join() print('子线程执行完毕,回到主线程%s' % os.getpid())
结果:
当前线程(父线程)的ID是: 1291子线程 test ,ID是:1292子线程执行完毕,回到主线程1291
1.2 使用进程池Pool
如果要启动多个子进程,则可用进程池Poll
,例如
代码:
from multiprocessing import Poolimport osimport timeimport randomdef child_task(name): print('子进程 %s ID是:%s 正在运行' % (name, os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print('子进程 %s 运行了 %0.2f 秒' % (name, (end - start)))if __name__ == '__main__': print('当前进程(父进程)ID是:%s' % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(child_task, args=(i,)) print('子进程循环创建完毕,正在等待子进程执行。。') p.close() p.join() print('所有进程运行完毕')
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
结果:
子进程循环创建完毕,正在等待子进程执行。。子进程 0 ID是:1776 正在运行子进程 1 ID是:1777 正在运行子进程 2 ID是:1778 正在运行子进程 3 ID是:1779 正在运行子进程 1 运行了 0.24 秒子进程 4 ID是:1777 正在运行子进程 3 运行了 0.64 秒子进程 2 运行了 0.79 秒子进程 0 运行了 1.21 秒子进程 4 运行了 1.56 秒所有进程运行完毕
1.2 进程之间的通信
进程与进程之间通过传递对象Queue
来通信,例如
代码:
from multiprocessing import Process, Queueimport osimport timeimport randomdef write(q): for value in ['A', 'B', 'C']: print('进程 %s 开始写入 %s' % (os.getpid(), value)) q.put(value) time.sleep(random.random()) def read(q): while True: value = q.get(True) print('进程 %s 开始读出 %s' % (os.getpid(), value))if __name__ == '__main__': q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) pw.start() pr.start() pw.join() pr.terminate()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
结果:(开启了两个进程,一个读一个写)
进程 1984 开始写入 A进程 1985 开始读出 A进程 1984 开始写入 B进程 1985 开始读出 B进程 1984 开始写入 C进程 1985 开始读出 C
2.多线程
2.1 创建多线程
Python 的多线程用到的是threading
模块,同启动进程类似,传入要执行的函数,例如.
代码:
import time, threadingdef loop(): print('创建了线程 %s' % threading.current_thread().name) for n in range(5): print('线程 %s 循环第 %s 次' % (threading.current_thread().name, n + 1)) time.sleep(1) print('线程 %s 结束' % threading.current_thread().name)print('最开始线程 %s 正在执行' % threading.current_thread().name)t = threading.Thread(target=loop, name='LoopThread') t.start()t.join() print('线程 %s 结束' % threading.current_thread().name)
结果:(主线程实例的名字叫MainThread)
最开始线程 MainThread 正在执行创建了线程 LoopThread线程 LoopThread 循环第 1 次线程 LoopThread 循环第 2 次线程 LoopThread 循环第 3 次线程 LoopThread 循环第 4 次线程 LoopThread 循环第 5 次线程 LoopThread 结束线程 MainThread 结束
2.1 线程锁Lock
多进程中,各自的进程变量都是各自进程独有的,进程之间互不影响,而线程中,所有线程都可以对同一个变量进行修改,这样就容易造成数据混乱。
代码:
import threadingimport timebalance = 0 def change_it(n): global balance balance = balance + n balance = balance - ndef run_thread(n): for i in range(100000): change_it(n)t1 = threading.Thread(target=run_thread, args=(5,))t2 = threading.Thread(target=run_thread, args=(8,))t1.start()t2.start()t1.join()t2.join()print(balance)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
多次运行后,结果可能就不是0了,因为两个线程交叉执行,造成了数据混乱,可用Lock
锁来确定,某个时间只能有一个线程执行该语句,例如
代码:
lock = threading.Lock()...def run_thread(n): for i in range(100000): lock.acquire() try: change_it(n) finally: lock.release()
线程上锁的好处就是确保某时间段内只有该线程执行,缺点就是无法并发执行线程,效率较低。另可存在多个锁,有可能造成死锁的情况
2.3 python中的多核
Python线程执行前都会有一个GIL
锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,因此多线程在python中,只能用一个核,若要实现多核功能,可用多进程模式
3.Python中的ThreadLocal
上面已经写过,多个线程对全局变量同时做修改时会造成数据混乱,可添加互斥所来控制同一时间只有一个线程访问全局变量,但是很多时候各个线程还有自己的私有变量,如下。
代码:
import threadingdef show(num): print ('线程 %s 的结果是: %s' % (threading.current_thread().getName(), num))def test_add(): result = 0 for _ in xrange(1000): result += 1 show(result) threads = []for i in range(5): threads.append(threading.Thread(target=test_add, name=(i + 1))) threads[i].start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
结果:(各自线程有各自的私有变量)
Thread-1 的结果是: 1000Thread-2 的结果是: 1000Thread-3 的结果是: 1000Thread-4 的结果是: 1000Thread-5 的结果是: 1000
若在调用show()
函数的时候还要传入其他私有变量,或者还有很多类似show()
函数需要调用私有变量,这样每个函数都传递私有变量太繁琐,可在全局定义一个字典,某个线程创建的时候将线程的私有变量(key=线程实例,value=变量值)加入到字典中,这样该线程内部的其他函数调用私有变量的时候不用来回传递也可取到该值,例如
代码:
import threadingdata = {} def show(): thread = threading.current_thread() print ('线程 %s 的结果是: %s' % (thread.getName(), data[thread]))def test_add(): thread = threading.current_thread() data[thread] = 0 for _ in xrange(1000): data[thread] += 1 show() threads = []for i in range(5): threads.append(threading.Thread(target=test_add, name=(i + 1))) threads[i].start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
结果:
线程 1 的结果是: 1000线程 2 的结果是: 1000线程 3 的结果是: 1000线程 4 的结果是: 1000线程 5 的结果是: 1000
然而上边的代码并没有做到完全的线程和数据分隔开,而且,每个线程执行的时候都可以访问全局字典,即可以修改其他值,这样可能也会造成数据混乱,因此,可以使用ThreadLocal
,其实也就是相当于全局字典,只是封装好了key值,只可以访问和修改相应的value值,例如
代码:
import threadingdata = threading.local() def show(): thread = threading.current_thread() print ('线程 %s 的结果是: %s' % (thread.getName(), data.num)) def test_add(): data.num = 0 for _ in xrange(1000): data.num += 1 show()threads = []for i in range(5): threads.append(threading.Thread(target=test_add, name=(i + 1))) threads[i].start()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
结果是一样的