进程补充和线程

Posted godlover

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程补充和线程相关的知识,希望对你有一定的参考价值。

进程补充和线程

1.子进程回收资源的两种方式:
1)join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源

? 2) 主进程“正常结束”,子进程与主进程一并被回收资源

2.僵尸进程与孤儿进程

僵尸进程(有坏处):

在子进程结束后,主进程没有正常结束,子进程PID不会被回收

缺点:

操作系统中的PID号是有限的,如果有子进程pid号无法正常回收,则会占用pid号

资源会浪费,若pid号满了,则无法创建新的进程

孤儿进程(无坏处):
在子进程没有结束时,主进程没有“正常结束”,子进程pid不会被回收

但是在操作系统中:

操作系统优化机制:

? 当主程序意外终止时,操作系统会检测是否有正在运行的子进程,会把他们放入孤儿院中,让操作系统帮你自动回收

进程的属性与方法:

from multiprocessing imprt Process
from multiprocessing import current_process
import os
import time
# os.getpid()查看主进程的pid
# os.getppid()查看主主进程的pid
# 在子进程中调用,可以拿到子进程对象.pid可以拿到pid号
# 在主进程中调用,可以拿到主进程对象.pid可以拿到pid号


def task():
    print(f'start.....{current_process().pid}')
    time.sleep(1000000)
    print(f'end......{current_process().pid}')
    print('子进程结束!')


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    # p.join()

    print(f'进入主进程的io{os.getpid()}')
    time.sleep(4)
    print(f'主主进程结束!{os.getppid()}')
    f = open('tank.txt')
    
    
# 孤儿进程演示
cmd中
Microsoft Windows [版本 10.0.18362.476]
(c) 2019 Microsoft Corporation。保留所有权利。

C:UsersAdministrator>python F:python_workpython_oldboyedu_learnday30僵尸进程与孤儿进程.py
进入主进程的io13072
start.....3940
主进程结束!16668
Traceback (most recent call last):
  File "F:python_workpython_oldboyedu_learnday30僵尸进程与孤儿进程.py", line 23, in <module>
    f = open('tank.txt')
FileNotFoundError: [Errno 2] No such file or directory: 'tank.txt'

查看pid是否被操作系统优化机制回收

Microsoft Windows [版本 10.0.18362.476]
(c) 2019 Microsoft Corporation。保留所有权利。

C:UsersAdministrator>tasklist |findstr "13072"
python.exe                   13072 Console                    1     12,328 K

C:UsersAdministrator>tasklist |findstr "3940"
python.exe                    3940 Console                    1     12,396 K

C:UsersAdministrator>tasklist |findstr "16668"
cmd.exe                      16668 Console                    1      4,024 K

C:UsersAdministrator>

    
# 僵尸进程演示
from multiprocessing import Process
from multiprocessing import current_process

import os
import time


def task():
    print(f'start.....{current_process().pid}')
    time.sleep(30)
    print(f'end......{current_process().pid}')
    print('子进程结束!')


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()

    print(f'进入主进程的io{os.getpid()}')
    time.sleep(20)
    print(f'主进程结束!{os.getppid()}')
    time.sleep(20000)
    f = open('tank.txt')
    
    cmd演示
    
    Microsoft Windows [版本 10.0.18362.476]
(c) 2019 Microsoft Corporation。保留所有权利。

C:UsersAdministrator>python F:python_workpython_oldboyedu_learnday30僵尸进程与孤儿进程.py
start.....1004
end......1004
子进程结束!
进入主进程的io5644
主进程结束!13084

查看pid是否被操作系统优化机制回收

Microsoft Windows [版本 10.0.18362.476]
(c) 2019 Microsoft Corporation。保留所有权利。

C:UsersAdministrator>tasklist |findstr "1004"

C:UsersAdministrator>tasklist |findstr "5644"
python.exe                    5644 Console                    1     12,248 K

C:UsersAdministrator>tasklist |findstr "13084"
cmd.exe                      13084 Console                    1      4,020 K

C:UsersAdministrator>

3.进程与进程之间是相互隔离的

4.守护进程

当主进程结束时,子进程也必须结束,并回收

守护进程随主进程结束而结束演示

from multiprocessing import  Process
import time


def demo(name):
    print(f'start...{name}')
    time.sleep(1000)
    print(f'end...{name}')


if __name__ == '__main__':
    p = Process(target=demo, args=('jason',))

    # 守护进程必须在p.start()之前调用
    p.daemon = True  # 将子进程设置为守护进程

    p.start()
    time.sleep(2)
    print('皇帝结束了!')
    
    
console:

start...jason
皇帝结束了!

Process finished with exit code 0

5.进程互斥锁:

? 互斥锁是一把锁,用来保护数据读写的安全

下面进行抢票举例:

from multiprocessing import Process
from multiprocessing import Lock
# 进程互斥锁
import random
import time
import json


# 抢票例子,不上锁会导致都显示抢到了,但实则只有一人抢到
def search(name):
    with open('data_ticket.json', 'r', encoding='utf-8') as f:
        data_dic = json.load(f)
        print(f'用户{name}查看了余票,余票还剩余:{data_dic.get("number")}')


def buy(name):
    with open('data_ticket.json', 'r', encoding='utf-8') as f:
        data_dic = json.load(f)
        if data_dic.get('number') > 0:
            data_dic['number'] -= 1
            time.sleep(random.random())
            time.sleep(1)
            with open('data_ticket.json', 'w', encoding='utf-8') as f:
                json.dump(data_dic, f)
                print(f'用户{name}, 抢票成功!')
        else:
            print(f'用户{name}, 抢票失败!')


def run(name):
    search(name)
    buy(name)


if __name__ == '__main__':
    # 开启多线程,实现并发
    for line in range(10):
        p_obj = Process(target=run, args=(f'jason{line} ',))
        p_obj.start()
        
# 结果演示
用户jason0 查看了余票,余票还剩余:1
用户jason2 查看了余票,余票还剩余:1
用户jason1 查看了余票,余票还剩余:1
用户jason3 查看了余票,余票还剩余:1
用户jason4 查看了余票,余票还剩余:1
用户jason5 查看了余票,余票还剩余:1
用户jason6 查看了余票,余票还剩余:1
用户jason7 查看了余票,余票还剩余:1
用户jason8 查看了余票,余票还剩余:1
用户jason9 查看了余票,余票还剩余:1
用户jason3 , 抢票成功!
用户jason2 , 抢票成功!
用户jason0 , 抢票成功!
用户jason1 , 抢票成功!
用户jason6 , 抢票成功!
用户jason5 , 抢票成功!
用户jason4 , 抢票成功!
用户jason7 , 抢票成功!
用户jason9 , 抢票成功!
用户jason8 , 抢票成功!
        
        
# 上锁代码
from multiprocessing import Process
from multiprocessing import Lock
# 进程互斥锁
import random
import time
import json


# 抢票例子,进行上锁
def search(name):
    with open('data_ticket.json', 'r', encoding='utf-8') as f:
        data_dic = json.load(f)
        print(f'用户{name}查看了余票,余票还剩余:{data_dic.get("number")}')


def buy(name):
    with open('data_ticket.json', 'r', encoding='utf-8') as f:
        data_dic = json.load(f)
        if data_dic.get('number') > 0:
            data_dic['number'] -= 1
            # time.sleep(random.random())
            with open('data_ticket.json', 'w', encoding='utf-8') as f:
                json.dump(data_dic, f)
                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):
        p_obj = Process(target=run, args=(f'jason{line} ', lock))
        p_obj.start()
        
结果展示:
用户jason0 查看了余票,余票还剩余:1
用户jason0 , 抢票成功!
用户jason1 查看了余票,余票还剩余:0
用户jason1 , 抢票失败!
用户jason2 查看了余票,余票还剩余:0
用户jason2 , 抢票失败!
用户jason4 查看了余票,余票还剩余:0
用户jason4 , 抢票失败!
用户jason3 查看了余票,余票还剩余:0
用户jason3 , 抢票失败!
用户jason5 查看了余票,余票还剩余:0
用户jason5 , 抢票失败!
用户jason6 查看了余票,余票还剩余:0
用户jason6 , 抢票失败!
用户jason7 查看了余票,余票还剩余:0
用户jason7 , 抢票失败!
用户jason8 查看了余票,余票还剩余:0
用户jason8 , 抢票失败!
用户jason9 查看了余票,余票还剩余:0
用户jason9 , 抢票失败!

6.队列

先进先出:先存放的数据就先被取出来,相当于一个第三方的管道,可以存放数据

目的:是让进程间的数据进行交互

三种方式:
from multiprocessing import Queue # 这是multiprocessing提供队列,先进先出
from multiprocessing import JoinableQueue # 基于Queue封装的队列, 先进先出
import queue # python内部提供的队列 先进先出

一.
Queue(5)
# 指的是队列中只能存放5份数据
q_obj = Queue(5)
q_obj.put('jason')
...
只要队列满了,就会进入阻塞
这里的阻塞态,获取不到值,会一直挂起,导致后面的代码无法执行
# 这里要注意,阻塞态结束了才能够进入就绪态
q_obj.put_nowait('jason')
只要队列满了,就会报错

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

q_obj.get_nowait()
若队列中没有数据则会报错

二.
q_obj = JoinableQueue(5)
# 添加数据到队列中
q_obj.put('jason')
...
同上

三.
q_obj = queue.Queue(5)
...
同上

7.IPC机制(进程间实现通信)

from multiprocessing import Process
from multiprocessing import JoinableQueue
import time


def task1(q):
    q.put(12)
    time.sleep(3)
    print('这里是进程1!')
    print(q.get())


def task2(q):
    # time.sleep(1)
    print('这里是进程2!')
    res = q.get()
    print(res)
    q.put(1024)


if __name__ == '__main__':
    q = JoinableQueue(3)
    p1 = Process(target=task1, args=(q, ))
    p2 = Process(target=task2, args=(q, ))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('主进程 is over!')
    
执行结果:
这里是进程2!
12
这里是进程1!
1024
主进程 is over!

8.生产者与消费者

生产者: 生产数据的

消费者: 使用数据的

通过队列来实现,解决供需不平衡的问题

from multiprocessing import Process
from multiprocessing import JoinableQueue
from multiprocessing import Lock
import time


# 生产者生产数据
def producer(name, food, q, lock):
    lock.acquire()
    q.put(food)
    print(f'{name},生产了一个{food}')
    lock.release()


# 消费者消费数据
def customer(name, q):
    while True:
        try:
            time.sleep(1)
            res = q.get_nowait()
            print(f'{name}消费了{res}')
        except Exception as e:
            print(e)
            break


if __name__ == '__main__':
    lock = Lock()
    q = JoinableQueue(5)
    list1 = []
    for i in range(5):
        p1 = Process(target=producer, args=('sean', f'美食{i+1}', q, lock))
        p1.start()
        list1.append(p1)
    for line in list1:
        line.join()
    c1 = Process(target=customer, args=('tank', q))
    c2 = Process(target=customer, args=('jason', q))
    c1.start()
    c2.start()
    c1.join()
    c2.join()
    print('主进程结束!')
    
sean,生产了一个美食2
sean,生产了一个美食1
sean,生产了一个美食4
sean,生产了一个美食3
sean,生产了一个美食5
tank消费了美食2
jason消费了美食1
tank消费了美食4
jason消费了美食3
tank消费了美食5


主进程结束!

9.线程

什么是线程:

进程是资源单位,线程是执行单位

进程与线程都是虚拟的概念,为了更好的表达某一种事物

注意:开启一个进程,一定会自带一个线程,线程才是真正的执行者

为什么要使用线程:

可以节省资源的占用

开启进程:

1.会产生一个内存空间,申请出一块资源

2.会自带一个主线程

3.开启子进程的速度,要比开启子线程的速度慢

开启线程:

1.一个进程可以开启多个线程,从进程的内存空间中,申请执行单位

2.节省资源

io密集型:

建议使用多线程

计算密集型:

建议使用多进程

注意:进程与进程之间的数据是隔离的而线程与线程之间的数据是可以共享的

守护线程

from threading import current_thread
两种方式与进程一样的,将Process用Thread来替换,
在t.start()之间加上t.daemon = True



from threading import current_thread
from threading import Thread

import time


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


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


if __name__ == '__main__':
    t1 = Thread(target=task1)
    t2 = Thread(target=task2)
    t1.daemon = True
    t1.start()
    t2.start()
    # time.sleep(2)
    print('主线程结束了!')


结果:
task1 start...
task2 start...
主线程结束了!
task2 end...

10.线程互斥锁

#  开启石十个线程,对一个数据进行修改
from threading import Thread
from threading import Lock
import time

num = 100


def task(lock):
    lock.acquire()
    global num
    n = num
    time.sleep(1)
    num = n - 1
    lock.release()


if __name__ == '__main__':
    lock = Lock()
    list1 = []
    for i in range(10):
        t = Thread(target=task, args=(lock, ))
        t.start()
        list1.append(t)
    for i in list1:
        i.join()
    print(num)
    print('主进程结束!')

    90
主进程结束!



#  开启石十个线程,对一个数据进行修改
from threading import Thread
from threading import Lock
import time

num = 100


def task():
    # lock.acquire()
    global num
    n = num
    time.sleep(1)
    num = n - 1
    # lock.release()


if __name__ == '__main__':
    # lock = Lock()
    list1 = []
    for i in range(10):
        t = Thread(target=task, )
        t.start()
        list1.append(t)
    for i in list1:
        i.join()
    print(num)
    print('主进程结束!')

    
结果:
99
主进程结束!

11.线程池

限制创建线程的个数

# 线程池限制线程数量
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread


def task(name):
    import time
    print('task start ....')
    print(f'{current_thread()}')
    time.sleep(1)
    print('task end ....')
    print(f'{name}')


def task1(name):
    print('1')
    print(f'{name}')


if __name__ == '__main__':
    pool = ThreadPoolExecutor(2)
    for i in range(10):
        pool.submit(task, 'libai')
        pool.submit(task1, 'libai')

以上是关于进程补充和线程的主要内容,如果未能解决你的问题,请参考以下文章

线程学习知识点总结

2020Python修炼记python并发编程补充—进程池和线程池

进程和线程的区别, 面相对象补充, 进程, 数据共享, 锁, 进程池, 爬虫模块(requests, bs4(beautifulsoup))

多线程编程

多个请求是多线程吗

JAVA多线程及补充