并发编程 多进程&&多线程

一.  进程(进程间数据是相互隔离的)

1. 开启子进程的两种方式(整个py文件为主进程)

p = Process(target='函数名',grgs=(函数的参数,)

p.start()    主进程向操作系统发送提交开启子进程的信号

p.join()    主进程等待子进程执行完毕(阻塞态)

# 开启子进程的方式一:    常用   导入模块   实例一个对象
'''
from multiprocessing import Process
import time

def task(name):
    print('%s is running' %name)
    time.sleep(3)
    print('%s is done' %name)

# 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中
if __name__ == '__main__':
    p=Process(target=task,args=('egon',)) #Process(target=task,kwargs={'name':'egon'})
    p.start() # 只是向操作系统发送了一个开启子进程的信号
    print('主')
'''

# 开启子进程的方式二:
from multiprocessing import Process
import time

class Myprocess(Process):
    def __init__(self,name):
        super().__init__()
        self.name=name

    def run(self):
        print('%s is running' %self.name)
        time.sleep(3)
        print('%s is done' %self.name)

# 在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中
if __name__ == '__main__':
    p=Myprocess('egon')
    p.start() # 只是向操作系统发送了一个开启子进程的信号
    print('')

2.进程间的内存空间是相互隔离的

from multiprocessing import Process

n=100
def task():
    global n
    n=0

if __name__ == '__main__':      
    p=Process(target=task)    #
    p.start()  #主进程向操作系统发送开启子进程的信号
    p.join()  # 主进程等待子进程运行完毕
    print(n)  #100

3.join的用法 (主进程等待子进程运行完毕)

from multiprocessing import Process
import time

def task(name,n):
    print('%s is running' %name)
    time.sleep(n)
    print('%s is done' %name)

if __name__ == '__main__':
    
    start=time.time()
    p_l=[]
    for i in range(1,4):
        p = Process(target=task, args=('子%s' %i, i))
        p_l.append(p)     
        p.start()           #并发的时候不按顺序

    for p in p_l:
        p.join()
    print('', (time.time() - start))

4.进程对象其它相关属性或方法 

 os.getpid()  获取当前进程的进程号

 p.is_alive()  判断进程是否还活着   

 p.terminate()   杀死进程

#2. 进程对象其他相关的属性或方法
from multiprocessing import Process,current_process
import time,os

def task():
    print('%s is running 爹是:%s' %(os.getpid(),os.getppid()))
    time.sleep(30)
    print('%s is done 爹是:%s' %(os.getpid(),os.getppid()))


if __name__ == '__main__':
    p=Process(target=task,name='子进程1')
    p.start()
    # print(p.name)
    p.terminate()      #杀死进程
    # time.sleep(0.1)
    print(p.is_alive())
    print('主:%s 主他爹:%s' %(os.getpid(),os.getppid()))

5.子进程回收的两种方式:

1.通过主进程p.jion() 回收
2.父进程结束后,操作系统会将子进程一并回收

6.僵尸进程与  孤儿进程

僵尸进程:主进程没死(死循环了),子进程运行完成之后,资源没有被主进程回收,占用资源,  解决方法:杀掉主进程,资源交给系统统一回收
孤儿进程:子进程还没运行完成,资源没有被父进程回收,这时候父进程挂掉了,子进程就成为孤儿进程
僵尸进程有害,孤儿进程无害

7.守护进程:

就是子进程去守护父进程,主进程结束,子进程也跟着结束

  方式 :在子程序启动之前,即p1.start()之前,加上p1.daemon = True

from multiprocessing import  Process
import  time
import os

def  demo(name):
    print(f'start...{name}')
    time.sleep(100)
    print(f'end...{name}')
    print('子进程结束了')
    print('子进程编号:',os.getpid())

if __name__ == '__main__':
    p1 = Process(target=demo,args=('子进程1',))
    #将子进程设置为守护进程
    p1.daemon = True
    p1.start()
    #p1.join()
    time.sleep(3)
    print('主进程结束')
    print('主进程编号:', os.getpid())

8. 互斥锁

主要目的 是为了把并发变成串行进程锁

   方式:1. 导入LOCK模块

              2. 在创建进程对象时,设置args的一个参数为lock ==》 LOCK()    并锁定数据修改部分  "    lock.acquire() +函数修改数据部分 + lock.release()    " 

from  multiprocessing import Process
from  multiprocessing import Lock
import json
import time
import random
'''
进程锁的主要目的 是为了把并发变成串行
进程锁 在创建进程对象时,设置args的一个参数为lock ==》    LOCK()
然后用lock.acquire() 函数 lock.release() 锁定或释放函数
'''

#1.查看余票
def  search(name):
    with open('data.json','r',encoding='utf-8') as f:
        data_dict = json.load(f)
        print(f'用户[{name}]查看余票,余票还剩:{data_dict.get("number")}!')

#2.若有余票,购买成功,票数会减少

def buy(name):
    with open('data.json','r',encoding='utf-8') as f1:
        data_dict = json.load(f1)

    if data_dict.get('number') > 0:
        data_dict['number'] -=1

        #模拟网络延迟    
        time.sleep(random.randint(1,3))
        with open('data.json','w',encoding = 'utf-8') as f2:
            json.dump(data_dict,f2)
        print(f'用户[{name}],抢票成功!!!')

    else:
        print(f'用户[{name}],抢票失败')

def run(name,lock):

    search(name)
    # 如果不加锁,所有的人都能抢票成功
    lock.acquire()
    buy(name)
    lock.release()



if __name__ == '__main__':
    lock = Lock()
    #开启多进程实现并发
    for line in range(10):
        p1 = Process(target= run,args =(f'jason{line}',lock))
        # p1 = Process(target=run, args=(f'jason{line}', lock))
        p1.start()

9.队列    相当与共享内存 可以存取数据

进程间通信(IPC机制)

原则:先进先出 管道+锁   主要用来实现进程间的通信   

常用方法:    put 只要队列满了 就会进入阻塞态    put _nowait   队列满了会报错  

                      get:只要队列中有数据,就能获取数据,若没有数据则会进入阻塞态    get_nowait()  若无数据直接报错

from  multiprocessing  import  Queue
from multiprocessing  import  JoinableQueue
import queue

#第一种
q_obj = Queue(2)
q_obj.put('json')
q_obj.put('tank')
#q_obj.put('www')   # put 只要队列满了 就会进入阻塞态

#q_obj.put_nowait('www')  #  只要队列满了 就会报错

print(q_obj.get())  #只要队列中有数据,就能获取数据,若没有数据则会进入阻塞态
print(q_obj.get())
print(q_obj.get())

#print(q_obj.get_nowait())

#第二种
q_obj1 = JoinableQueue(5)

#第三种用法相同
q_obj2 = queue.Queqe(5)

进程间通信(IPC机制)

from multiprocessing import Process
from multiprocessing import JoinableQueue
import time


def task1(q):
    x = 100
    q.put(x)
    print('添加数据')

    time.sleep(3)
    print(q.get())


def task2(q):
    # 想要在task2中获取task1的x
    res = q.get()
    print(f'获取的数据是{res}')
    q.put(9527)


if __name__ == '__main__':
    # 产生队列
    q = JoinableQueue(10)

    # 产生两个不同的子进程
    p1 = Process(target=task1, args=(q, ))
    p2 = Process(target=task2, args=(q, ))

    p1.start()
    p2.start()

10.生产者与消费者模型

    生产者: 生产数据 ---》 队列  ---》消费者:使用数据

from multiprocessing import JoinableQueue
from multiprocessing import Process
import time


# 生产者: 生产数据 ---》 队列
def producer(name, food, q):
    msg = f'{name} 生产了 {food} 食物'
    # 生产一个食物,添加到队列中
    q.put(food)
    print(msg)


# 消费者: 使用数据 《---  列队
def customer(name, q):
    while True:
        try:
            time.sleep(0.5)
            # 若报错,则跳出循环
            food = q.get_nowait()
            msg = f'{name} 吃了 {food} 食物!'
            print(msg)

        except Exception:
            break


if __name__ == '__main__':
    q = JoinableQueue()

    # 创建两个生产者
    for line in range(10):
        p1 = Process(target=producer, args=('tank1', f'Pig饲料{line}', q))
        p1.start()

    # 创建两个消费者
    c1 = Process(target=customer, args=('jason', q))
    c2 = Process(target=customer, args=('sean', q))
    c1.start()
    c2.start()

二. 线程 (线程之间数据是共享的)

   进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。每个进程都有一个主线程

线程:  线程中没有主线程和子线程,所有的线程都是一样的

1 什么是线程
    进程其实一个资源单位,而进程内的线程才是cpu上的执行单位
    线程其实指的就是代码的执行过程

2 为何要用线程
    线程vs进程
        1. 同一进程下的多个线程共享该进程内的资源
        2. 创建线程的开销要远远小于进程

 1. 创建线程的两种方式:   from  threading import  Thread

from threading import Thread
import time
#
# number = 1000
#
#
# 启动线程的方式一:
# 任务1:
# def task():
#     global number
#     number = 100
#     print('start...')
#     time.sleep(1)
#     print('end...')
#
#
# if __name__ == '__main__':
#     # 开启一个子线程
#     t = Thread(target=task)
#     t.start()
#     # t.join()
#     print('主进程(主线程)...')
#     print(number)


# 启动线程的方式二:
# class MyThread(Thread):
#     def run(self):
#         print('start...')
#         time.sleep(1)
#         print('end...')
#
#
# if __name__ == '__main__':
#     # 开启一个子线程
#     t = MyThread()
#     t.start()
#     # t.join()
#     print('主进程(主线程)...')

2.守护线程

# from threading import Thread
# import time
#
# def task(name):
#     print('%s is running' %name)
#     time.sleep(2)
#     print('%s is done' %name)
#
# if __name__ == '__main__':
#     t=Thread(target=task,args=('线程1',))
#     t.daemon=True
#     t.start()
#     print('主')

3.线程锁

from threading import Thread,Lock
import time

mutex=Lock()
n=100
def task():
    global n
    mutex.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    mutex.release()

if __name__ == '__main__':
    t_l=[]
    for i in range(100):
        t=Thread(target=task)
        t_l.append(t)
        t.start()

    for t in t_l:
        t.join()
    print(n)

4.GIL(全局解释器锁)

GIL全局解释器锁: 是解释器锁,python的开发者的解释锁机制 ,因为python的内存管理机制不是内存安全的,在多线程工作时,GIL锁为了保证数据安全

                                       python 会用解释器锁,把并发变成串行,导致无法使用多核优势

       结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。

优化方法:多核情况下:计算机密集型多进程,I/O密集型多线程

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核情况下,分析结果: 
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

#多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

 
#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

实例比较

# 计算密集型任务
def task1():
    # 计算1000000次 += 1
    i = 10
    for line in range(10000000):
        i += 1


# IO密集型任务
def task2():
    time.sleep(3)


if __name__ == '__main__':
    # 1、测试多进程:
    # 测试计算密集型
    start_time = time.time()
    list1 = []
    for line in range(6):
        p = Process(target=task1)
        p.start()
        list1.append(p)

    for p in list1:
        p.join()
    end_time = time.time()
    # 消耗时间: 0.44082188606262207
    print(f'多进程计算密集型消耗时间: {end_time - start_time}')

    # 测试IO密集型
    start_time = time.time()
    list1 = []
    for line in range(6):
        p = Process(target=task2)
        p.start()
        list1.append(p)

    for p in list1:
        p.join()
    end_time = time.time()
    # 消耗时间: 0.44082188606262207
    print(f'多进程IO密集型消耗时间: {end_time - start_time}')


    # 2、测试多线程:
    # 测试计算密集型
    start_time = time.time()
    list1 = []
    for line in range(6):
        p = Thread(target=task1)
        p.start()
        list1.append(p)

    for p in list1:
        p.join()
    end_time = time.time()
    # 消耗时间: 0.44082188606262207
    print(f'多线程计算密集型消耗时间: {end_time - start_time}')

    # 测试IO密集型
    start_time = time.time()
    list1 = []
    for line in range(6):
        p = Thread(target=task2)
        p.start()
        list1.append(p)

    for p in list1:
        p.join()
    end_time = time.time()
    # 消耗时间: 0.44082188606262207
    print(f'多线程IO密集型消耗时间: {end_time - start_time}')


多进程计算密集型消耗时间: 1.0132722854614258
多进程IO密集型消耗时间: 3.190805196762085
多线程计算密集型消耗时间: 3.1894755363464355
多线程IO密集型消耗时间: 3.0030970573425293
View Code

 5.死锁与递归锁

所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('33[41m%s 拿到A锁33[0m' %self.name)

        mutexB.acquire()                             # 2.线程2 拿不到B锁,进入阻塞态
        print('33[42m%s 拿到B锁33[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('33[43m%s 拿到B锁33[0m' %self.name)
        time.sleep(2)                              #1.线程一暂停,线程二起来了

        mutexA.acquire()                           # 3.线程1暂停结束后,拿不到A锁,进入阻塞态
        print('33[44m%s 拿到A锁33[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''

解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

mutexA=mutexB=threading.RLock()
一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

6.进程池与线程池: 控制并发量

from concurrent.futures import ThreadPoolExecutor
import time

# pool只能创建100个线程

def task(line):
    print(line)
    time.sleep(10)

if __name__ == '__main__':
    pool = ThreadPoolExecutor(100)
#pool = ProcessPoolExcutor(100)
# for line in range(1000): # 执行10000次,100并发 # pool.submit(task, line) while True: pool.submit(task, line) #100并发一直执行

服务端并发实例

'''
服务端要实现多用户连接  并实现循环通信
'''

import socket
from concurrent.futures import ThreadPoolExecutor

server = socket.socket()

server.bind(
    ('127.0.0.1', 9000)
)

server.listen(5)


# 1.封装成一个函数   多用户实现循环通信   一个用户创建一个通道
def run(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            print(data.decode('utf-8'))
            conn.send('111'.encode('utf-8'))

        except Exception as e:
            break

    conn.close()


if __name__ == '__main__':
    print('Server is run....')
    pool = ThreadPoolExecutor(50)
    while True:
        conn, addr = server.accept()
        print(addr)
        pool.submit(run, conn)

7. 协程   

      目的:只针单线程IO操作才有意义,为了提升系统资源利用率,用代码实现 "遇到IO切换 + 保存状态" 去欺骗操作系统,让操作系统误以为没有IO操作,

                将CPU的执行权限给你   

      方法:通过gevent 模块的monkey 来监控IO操作,通过spawn 方法来创建并发线程,通过joinall来回收所有子线程

      补充:join的作用:等待子进程、线程结束后,主进程、线程再结束,||||   回收子进程或线程

# 遇到IO切换(gevent模块) + 保存状态
from gevent import monkey   #猴子补丁
monkey.patch_all()          #监听所有任务,是否有IO操作
from gevent import spawn    # spawn(任务)
from gevent import joinall
import time

def task1():
    print('start from task1...')
    time.sleep(1)
    print('end from task1...')

def task2():
    print('start from task2...')
    time.sleep(3)
    print('end from task2...')

def task3():
    print('start from task3...')
    time.sleep(5)
    print('end from task3...')

if __name__ == '__main__':
    start_time =time.time()
    sp1 = spawn(task1)
    sp2 = spawn(task2)
    sp3 = spawn(task3)
    joinall([sp1,sp2,sp3])
    end_time = time.time()
    print(end_time - start_time)

      

     

   

 

  

原文地址:https://www.cnblogs.com/bigbox/p/12004586.html