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

来源:互联网 发布:手机淘宝怎么搜索店铺 编辑:程序博客网 时间:2024/05/17 09:04

多进程和多线程的练习

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 Poolimport 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, Queueimport 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():    passprint("线程%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  和 t2t1 = 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 来确保锁一定会被释
放。

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执
行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代
码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可
以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,
可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,

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

全局锁的概念

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))    passdef process_thread(name):    # 绑定ThreadLoacl 的student    local_school.student=name    #调用线程需要执行的方法    process_student()    passprint("%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()
0 0