您的位置:首页 > 其它

多进程和多线程的练习笔记

2017-04-10 23:03 162 查看

多进程和多线程的练习

linux上创建进程

Unix/Linux 操作系统提供了一个 fork(). 系统调用fork()调用一次,返回两次,因

为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),

然后,分别在父进程和子进程内返回。

子进程永远返回 0,而父进程返回子进程的 ID。这样做的理由是,一个

父进程可以 fork 出很多子进程,所以,父进程要记下每个子进程的 ID,

而子进程只需要调用 getppid()就可以拿到父进程的 ID。

1 # 在linux上使用os模块的多进程fork方法
2 # coding=utf-8
3 import os
4 # 拿到父进程的pid号码
5 print("process(%s)start..."%os.getpid())
6 pid=os.fork()  # 拿到当前进程的pid号
7 if pid==0: # 说明是子进程
8     print("我是子进程:",os.getpid())
9     #子进程通过getppid()拿到父进程的pid
10     print("父进程的pid是:",os.getppid())
11 else:
12     print("父进程执行"+"=="*20)
13     print("我是父进程:",os.getpid())
14     print("我是子进程:",pid)
15     print("=="*20)
运行结果:
process(3436)start...
父进程执行========================================
我是父进程: 3436
我是子进程: 3437
========================================
我是子进程: 3437
父进程的pid是: 3436


在window下创建多进程

multiprocessing 模块提供了一个 Process 类来代表一个进程对象 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个

Process 实例,用 start()方法启动,

join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的

同步。

from multiprocessing import Process  # 导入需要的模块
import os
# 子进程要执行的代码
def run_proc(name):
print('子进程 %s (%s)...' % (name, os.getpid()))

# 就是说明当这个程序是在本程序中进行运行时才能执行下面的代码
if __name__ == '__main__':
# os.getpid()拿到进程的pid
print("线程开始",os.getpid())
# 创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process 实例,用 start()方法启动,

p = Process(target=run_proc, args=('test',)) # 这里的参数是一个元组
print('子线程将要开始执行')
p.start()  # 开始进程
p.join()  #  进程加入  只有等加入的进程结束了  主进程才能继续执行下面的代码
print('子线程执行完毕.')  # 这句话是主进程执行的


使用pool池来管理进程

如果要启动大量的子进程,可以用进程池的方式批量创建子进程

from multiprocessing import Pool
import os, time, random  # 导入  os  时间  随机模块
# 所有的子进程都需要执行这个程序  这个是传入的创建子进程的的方法中的函数
def long_time_task(name):
print('执行任务 %s (%s)...' % (name, os.getpid()))  # 拿到自己进程的pid号
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('任务 %s 正在执行 %0.2f seconds.' % (name, (end - start)))

if __name__ == '__main__':
print('父进程 %s.' % os.getpid())
p = Pool(5)  # 创建了能容纳4个线程的线程池
for i in range(5): # 遍历开始循环添加线程 到线程池
p.apply_async(long_time_task, args=(i,))
print('等待所有的子进程结束...')
p.close()  # 关闭线程池
p.join()  # 线程加入主进程开始执行
print('所有的子进程结束了')


对 Pool 对象调用 join()方法会等待所有子进程执行完毕,调用 join()

之前必须先调用 close(),调用 close()之后就不能继续添加新的 Process

了。

请注意输出的结果,task 0,1,2,3 是立刻执行的,而 task 4 要等待前

面某个 task 完成后才执行,这是因为 Pool 的大小在是 4

进程间的通信

”’

Process 之间肯定是需要通信的,操作系统提供了很多机制来实现进程

间的通信。Python 的 multiprocessing 模块包装了底层的机制,提供了

Queue、Pipes 等多种方式来交换数据

我们以 Queue 为例,在父进程中创建两个子进程,一个往 Queue 里写数

据,一个从 Queue 里读数据:

”’

from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('进程开始写: %s' % os.getpid())
for value in ['A', 'B', 'C','A1', 'B1', 'C1']:
print('把 %s 放入队列...' % value)
q.put(value)
time.sleep(random.random())
# 读数据执行的代码
def read(q):
print('进程开始读: %s' % os.getpid())
while True:
value = q.get(True)
time.sleep(random.random())
print('取出 %s 从队列.' % value)

if __name__ == '__main__':
# 父进程创建 Queue,并传给各个子进程:
q = Queue()
# 同时开始两个进程  执行不同的任务
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程 pw,写入:
pw.start()
# 启动子进程 pr,读取:
pr.start()
# 等待 pw 结束:
pw.join()
# pr 进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()


多线程的练习

Python 的标准库提供了两个模块:_thread 和 threading,_thread 是低级

模块,threading 是高级模块,对_thread 进行了封装。绝大多数情况下,

我们只需要使用 threading 这个高级模块。

启动一个线程就是把一个函数传入并创建 Thread 实例,然后调用 start()

开始执行:

代码演示:

# 多线程的练习
import time ,threading
'''
代码解析
threading.current_thread().name  拿到当前线程的名字

'''
# 子线程1的执行代码
def Loop1():
print("线程%s正在运行.."%threading.current_thread().name)
n=0
while n<5:  # 创建的线程循环执行  但是还是那一个线程
n+=1
print("线程%s>>>%s"%(threading.current_thread().name,n))
time.sleep(1)
print("线程%s结束."%threading.current_thread().name)
pass
# 同样可以定义线程2执行的程序
def loop2():
pass

print("线程%s正在运行.."%threading.current_thread().name)  # 主线程
t1=threading.Thread(target=Loop1,name="子线程1")  # 创建一个线程  线程执行的程序  传入线程的名称
t2=threading.Thread(target=Loop1,name="子线程2")  # 再创建一个线程  线程执行的程序  传入线程的名称
t1.start()  # 开启这个线程
t2.start()  # 开启这个线程
# t1.join()# 让线程加入  主线程等待子线程的结束  不开启的话线程就是随机运行的
# t2.join()# 让线程加入  主线程等待子线程的结束  不开启的话线程就是随机运行的
print("线程%s结束"%threading.current_thread().name)

'''
线程MainThread正在运行..
线程子线程1正在运行..
线程子线程1>>>1
线程子线程2正在运行..
线程子线程2>>>1
线程MainThread结束
线程子线程2>>>2
线程子线程1>>>2
线程子线程1>>>3
线程子线程2>>>3
线程子线程2>>>4
线程子线程1>>>4
线程子线程2>>>5
线程子线程1>>>5
线程子线程2结束.
线程子线程1结束.

'''


从运行的结果可以看出cpu在线程间是随机切换执行的,但是当加上join的时候 主线程会等待子线程结束的时候 才会结束

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线

程又可以启动新的线程,Python 的 threading 模块有个 current_thread()

函数,它永远返回当前线程的实例。主线程实例的名字叫 MainThread,

子线程的名字在创建时指定 可以随意指定 不指定系统会默认给定的名字

线程间锁的概念

lock

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份

拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线

程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程

之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱

了。 最后出现错误,锁的出现就是来控制线程,同一时刻只能有一个线程

去修改共享的变量

代码演示:

import time, threading

# 创建函数来修改这个变量
def changMoney(n):
print("%s好好学习"%threading.current_thread().name,end="")
time.sleep(0.01)
print("%s天天向上"%threading.current_thread().name)

# 定义线程 要执行的任务
def run_thread(n):
for i in range(100):
changMoney(n)

print("%s线程开始执行" % threading.current_thread().name)
# 创建线程 t1  和 t2
t1 = threading.Thread(target=run_thread, name="线程1", args=(5,))  # 传入的参数
t2 = threading.Thread(target=run_thread, name="线程2", args=(8,))
# 启动线程
t1.start()
t2.start()
# 让主线程等待
t1.join()
t2.join()
print("当前线程是:%s" % threading.current_thread().name)
执行的结果:(截取了一段)
线程1好好学习线程2天天向上
线程1天天向上
线程1好好学习线程2好好学习线程2天天向上
线程2好好学习线程1天天向上
线程1好好学习线程2天天向上


交替执行导致不能完整输入一句话

如果我们要确保输出完整,就要给 change_it()上一把锁,当某

个线程开始执行 change_it()时,我们说,该线程因为获得了锁,因此其

他线程不能同时执行 change_it(),只能等待,直到锁被释放后,获得该

锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一

个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过

threading.Lock()来实现:

import time, threading

# 创建一个锁对象
lock = threading.Lock()
# 定义线程 要执行的任务
def run_thread(n):
# 进入线程任务的时候  就需要这个进入的线程获取线程的锁

for i in range(100):
lock.acquire()  # 获取锁
try:
changMoney(n)
finally:
lock.release()  # 异常处理  就是确保当出现问题了  锁能及时的被释放  这样外边等待的线程就能 继续执行


获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远

等待下去,成为死线程。所以我们用 try…finally 来确保锁一定会被释

放。

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执

行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代

码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可

以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,

可能会造成死锁,导致多个线程全
b2b8
部挂起,既不能执行,也无法结束,

死锁就是两个线程同时拿了对方需要的锁 没有释放 导致两个线程同时被挂起了

全局锁的概念

GIL 锁:Global Interpreter Lock,任何 Python 线程执行前,必须先获得

GIL 锁,然后,每执行 100 条字节码,解释器就自动释放 GIL 锁,让别

的线程有机会执行。这个 GIL 全局锁实际上把所有线程的执行代码都给

上了锁,所以,多线程在 Python 中只能交替执行,即使 100 个线程跑

在 100 核 CPU 上,也只能用到 1 个核。

Python 虽然不能利用多线程实现多核任务,但

可以通过多进程实现多核任务。多个 Python 进程有各自独立的 GIL 锁,

互不影响。

多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又

要小心死锁的发生。

Python 解释器由于设计时有 GIL 全局锁,导致了多线程无法利用多核。

多线程的并发在 Python 中就是一个美丽的梦。 (多线程的并发)

ThreaLocal的用法

ThradLocal 为了解决全局变量在多线程使用的时候修改全局变量加锁的问题

ThreadLocal 最常用的地方就是为每个线程绑定一个数据库连接,HTTP

请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以

非常方便地访问这些资源。

一个 ThreadLocal 变量虽然是全局变量,但每个线程都只能读写自己线

程的独立副本,互不干扰。ThreadLocal 解决了参数在一个线程中各个函

数之间互相传递的问题。

import threading
#创建全局的 ThreadLocal 对象  #返回一个全局对象的
local_school=threading.local()

# 创建一个方法  需要线程执行的
def process_student():
#从线程ThreadLocal中获取存储在里面的student
std=local_school.student
#打印当前的绑定的名字和线程的名字
print("hello,%s(in %s)"%(std,threading.current_thread().name))
pass

def process_thread(name):
# 绑定ThreadLoacl 的student
local_school.student=name
#调用线程需要执行的方法
process_student()
pass
print("%s线程开始运行"%threading.current_thread().name)
#创建子线程
t1=threading.Thread(target=process_thread,name="子线程1",args=("小王",))
t2=threading.Thread(target=process_thread,name="子线程2",args=("小强",))
# 启动子线程
t1.start()
t2.start()
t1.join()
t2.join()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: