僵尸和孤儿进程、守护进程、进程和线程互斥锁、队列、进程池和线程池、回调函数

一、子进程回收资源的两种方式

- 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源。
- 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源。

二、僵尸进程和孤儿进程

僵尸进程 (有坏处):
- 在子进程结束后,主进程没有正常结束, 子进程PID不会被回收。

缺点:
- 操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号。
- 资源浪费。
- 若PID号满了,则无法创建新的进程。

孤儿进程(没有坏处):

- 在子进程没有结束时,主进程没有“正常结束”, 子进程PID不会被回收。
- 操作系统优化机制(孤儿院):
当主进程意外终止,操作系统会检测是否有正在运行的子进程,会他们放入孤儿院中,让操作系统帮你自动回收。

from multiprocessing import Process
from multiprocessing import current_process
# 在子进程中调用,可以拿到子进程对象.pid可以pid号
# 在主进程中调用,可以拿到主进程对象.pid可以pid号
import os

import time


# 任务
def task():
    print(f'start....{current_process().pid}')
    time.sleep(5)
    print(f'end......{os.getpid()}')
    print('子进程结束啦~~~')


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

    # 告诉操作系统帮你开启子进程
    p.start()
    # p.join()

    print(f'进入主进程的IO-->{current_process().pid}')
    time.sleep(1)
    print(f'进入主进程的IO-->{os.getpid()}')
    # 主进程结束
    print('主进程结束....')
    print(f'查看主主进程{os.getppid()}')
    f = open('tank.txt')

 

三、守护进程

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

from multiprocessing import Process
# 在子进程中调用,可以拿到子进程对象.pid可以pid号
# 在主进程中调用,可以拿到主进程对象.pid可以pid号

import time


# 任务
def demo(name):
    print(f'start....{name}')
    time.sleep(1000)
    print(f'end......{name}')
    print('子进程结束啦啊....~~~')


if __name__ == '__main__':
    p = Process(target=demo, args=('童子军jason1号', ))

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

    # 告诉操作系统帮你开启子进程
    p.start()
    # p.join()

    time.sleep(1)
    print('皇帝驾崩啦啊~~~')

 

from multiprocessing import Process

def foo():
    print(123)
    time.sleep(1)
    print("end123")

def bar():
    print(456)
    time.sleep(3)
    print("end456")


p1=Process(target=foo)
p2=Process(target=bar)

p1.daemon=True
p1.start()
p2.start()
time.sleep(0.1)
print("main-------")#打印该行则主进程代码结束,则守护进程p1应该被终止.#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.

 

 

四、进程间数据时隔离的

from multiprocessing import Process
n=100 

def work():
    global n  
    n=0
    print('子进程内: ',n)


if __name__ == '__main__':
    p=Process(target=work)
    p.start()
    print('主进程内: ',n)

 

 

五、进程互斥锁

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

from multiprocessing import Process,Lock
import time,json,random
def search():
    dic=json.load(open('db'))
    print('\033[43m剩余票数%s\033[0m' %dic['count'])

def get():
    dic=json.load(open('db'))
    time.sleep(random.random())  # 模拟读数据的网络延迟
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(random.random())  # 模拟写数据的网络延迟
        json.dump(dic,open('db','w'))
        print('\033[32m购票成功\033[0m')
    else:
        print('\033[31m购票失败\033[0m')

def task(lock):
    search()
    lock.acquire()  # 将买票这一环节由并发变成了串行,牺牲了运行效率但是保证了数据的安全
    get()
    lock.release()

if __name__ == '__main__':
    lock = Lock()
    for i in range(100):  # 模拟并发100个客户端抢票
        p=Process(target=task,args=(lock,))
        p.start()

 

 

六、队列

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

应用: 让进程之间数据进行交互。

 

from multiprocessing import Queue
q=Queue(3)  # 创建一个最大只能容纳3个数据的队列
"""
常用方法
put ,get ,put_nowait,get_nowait,full,empty
"""
q.put(3)  # 往队列中存放数据
q.put(3)
q.put(3)


q.put(3)  # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。如果队列中的数据一直不被取走,程序就会永远停在这里。
try:
    q.put_nowait(3) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。
except:  # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。
    print('队列已经满了')
    

# 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。
print(q.full())  # 判断队列中数据是否已存放满了

print(q.get())  # 从队列中获取数据
print(q.get())
print(q.get())


print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。
try:
    q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。
except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。
    print('队列已经空了')

    
print(q.empty())  # 判断队列中数据是否已经被全部取出

 

# 第二种
# q_obj1 = JoinableQueue(5)  # q_obj1队列对象
# # 添加数据到队列中
# q_obj1.put('jason')
# print('添加1个')
# q_obj1.put('hcy')
# print('添加1个')
# q_obj1.put('hb')
# print('添加1个')
# q_obj1.put('zsb')
# print('添加1个')
# q_obj1.put('lh')
# print('添加1个')
#
# # put: 只要队列满了,会进入阻塞
# # q_obj1.put('sean')
# # print('sean into ')
#
# # put_nowait: 只要队列满了,就会报错
# # q_obj1.put_nowait('sean')
#
# # get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞
# print(q_obj1.get())
# print(q_obj1.get())
# print(q_obj1.get())
# print(q_obj1.get())
# print(q_obj1.get())


# 第三种
# q_obj1 = queue.Queue(5)  # q_obj1队列对象
# # 添加数据到队列中
# q_obj1.put('jason')
# print('添加1个')
# q_obj1.put('hcy')
# print('添加1个')
# q_obj1.put('hb')
# print('添加1个')
# q_obj1.put('zsb')
# print('添加1个')
# q_obj1.put('lh')
# print('添加1个')

# put: 只要队列满了,会进入阻塞
# q_obj1.put('sean')
# print('sean into ')

# put_nowait: 只要队列满了,就会报错
# q_obj1.put_nowait('sean')

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

 

 

七、进程间通信(IPC机制)

基于队列实现进程通信

import time
from multiprocessing import Process, Queue
def f(q):
    q.put('hello')  #调用主函数中p进程传递过来的进程参数 put函数为向队列中添加一条数据。
if __name__ == '__main__':
    q = Queue()  # 创建一个Queue对象
    p = Process(target=f, args=(q,)) #创建一个进程
    p.start()
    print(q.get())  # 从队列中获取数据
    p.join()


from multiprocessing import Queue,Process
def producer(q):
    q.put('hello big baby!')
def consumer(q):
    print(q.get())
if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer,args=(q,))
    p.start()
    p1 = Process(target=consumer,args=(q,))
    p1.start()

 

 

八、生产者与消费者

- 生产者: 生产数据的
- 消费者: 使用数据的

- 生产油条的有人总比吃油条的人少 ---> 生产数据跟不上 使用数据的人 ---》 供需不平衡
- 吃油条的人比生产的油条要少 ---> 使用数据的速度 跟不上 生产数据的速度

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

from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('%s 吃 %s' %(os.getpid(),res))

def producer(q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('%s 生产了 %s' %(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()
    print('')

 

 

九、线程

1.什么是线程?
        进程: 资源单位。
        线程: 执行单位。

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

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


2.为什么要使用线程?
节省资源的占用。

- 开启进程:
          - 1) 会产生一个内存空间,申请一块资源。
          - 2) 会自带一个主线程
          - 3) 开启子进程的速度要比开启子线程的速度慢

- 开启线程
          - 1) 一个进程内可以开启多个线程,从进程的内存空间中申请执行单位。
          - 2) 节省资源。

           - 开启三个进程:
- 占用三份内存资源

- 开启三个线程:
              - 从一个内存资源中,申请三个小的执行单位

 - IO密集型用: 多线程
          - IO(时间由用户定):
                - 阻塞: 切换 + 保存状态

- 计算密集型用: 多进程
        - 计算(时间由操作系统定):
              - 计算时间很长 ---> 切换 + 保存状态


注意: 进程与进程之间数据是隔离的,线程与线程之间的数据是共享的。

3.怎么使用线程?

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('主进程(主线程)...')

# from threading import current_thread
#
# number = 1000
#
# def task():
#     global number
#     number = 100
#     print(f'start...{current_thread().name}')
#     time.sleep(3)
#     print(f'end...{current_thread().name}')
#
#
# if __name__ == '__main__':
#     # 开启一个子线程
#     for line in range(10):
#         t = Thread(target=task)
#         # 加上守护线程: 主进程结束,代表主线程也结束,子线程有可能未被回收。
#         t.daemon = True
#         t.start()
#
#     # t.join()
#     print(f'主进程(主线程)...{current_thread().name}')
#     print(number)

 

 

十、线程互斥锁

from threading import Lock
from threading import Thread
import time
lock = Lock()

# 开启10个线程,对一个数据进行修改
number = 100


def task():
    global number

    # lock.acquire()
    number2 = number
    # time.sleep(1)
    number = number2 - 1
    # lock.release()


if __name__ == '__main__':

    list1 = []
    for line in range(100000000):
        t = Thread(target=task)
        t.start()
        list1.append(t)

    for t in list1:
        t.join()

    print(number)  # 90

 

 

十一、线程池

线程池是用来限制 创建的线程数

 

# from concurrent.futures import ThreadPoolExecutor
# import time
#
# # pool只能创建100个线程
# pool = ThreadPoolExecutor(100)
#
#
# def task(line):
#     print(line)
#     time.sleep(10)
#
#
# if __name__ == '__main__':
#     for line in range(1000):
#         pool.submit(task, line)


# 通过并发(同步)爬虫某个网站的小视频
import requests
import re
# import os
# import uuid
#
#
# # 1.发送请求,获取响应数据
# def get_page(url):
#     response = requests.get(url)
#     if response.status_code == 200:
#         return response
#
#
# # 2.解析并提取主页id号
# def parse_page(response):
#     '''
#     https://www.pearvideo.com/video_1630253
#     https://www.pearvideo.com/video_1630042
#     '''
#     # 将所有电影的详情页id号,匹配获取,并放到列表中
#     id_list = re.findall('href="video_(.*?)"', response.text, re.S)
#     # print(len(id_list))
#     id_list = list(set(id_list))
#     # print(len(id_list))
#     return id_list
#
#
# def parse_detail(response):
#     '''
#     srcUrl="https://video.pearvideo.com/mp4/adshort/20191206/cont-1630253-14671892_adpkg-ad_hd.mp4"
#     srcUrl="(.*?)"
#     '''
#     mp4_url = re.findall('srcUrl="(.*?)"', response.text, re.S)
#     # print(mp4_url, 111111)
#     if mp4_url:
#         return mp4_url[0]
#
#
# # 3.保存数据
# def save_movie(movie_url):
#     response = get_page(movie_url)
#
#     movie_dir = r'D:\项目路径\python13期\day30\梨视频'
#     movie_path = os.path.join(
#         movie_dir, str(uuid.uuid4()) + '.mp4'
#     )
#     # print(movie_path)
#     with open(movie_path, 'wb') as f:
#         for line in response.iter_content():
#             f.write(line)
#
#
# if __name__ == '__main__':
#     response = get_page('https://www.pearvideo.com/')
#
#     # 解析提取所有电影详情页id号
#     id_list = parse_page(response)
#     # print(id_list)
#
#     # 循环拼接详情页链接
#     for id_num in id_list:
#         url = f'https://www.pearvideo.com/video_{id_num}'
#         # print(url)
#
#         # 往详情页发送请求,
#         detail_response = get_page(url)
#         # print(detail_response.text)
#
#         # # 解析电影详情页,并提取视频的存放的地址
#         mp4_url = parse_detail(detail_response)
#         print(mp4_url)
#
#         # # 发送请求获取视频真实数据
#         # movie_response = get_page(mp4_url)
#
#         # response.content
#         save_movie(mp4_url)



# 异步爬取梨视频
import requests
import re
import os
import uuid

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(100)


# 1.发送请求,获取响应数据
def get_page(url):
    print(f'发送get请求: {url}')
    response = requests.get(url)
    if response.status_code == 200:
        return response


# 2.解析并提取主页id号
def parse_page(response):
    '''
    https://www.pearvideo.com/video_1630253
    https://www.pearvideo.com/video_1630042
    '''
    # 将所有电影的详情页id号,匹配获取,并放到列表中
    id_list = re.findall('href="video_(.*?)"', response.text, re.S)
    # print(len(id_list))
    id_list = list(set(id_list))
    # print(len(id_list))
    return id_list


# 解析详情页,获取视频链接
def parse_detail(res):
    '''
    srcUrl="https://video.pearvideo.com/mp4/adshort/20191206/cont-1630253-14671892_adpkg-ad_hd.mp4"
    srcUrl="(.*?)"
    '''
    res2 = res.result()
    print(res2)

    movie_url = re.findall('srcUrl="(.*?)"', res2.text, re.S)
    print(movie_url)
    if movie_url:
        movie_url = movie_url[0]
        pool.submit(save_movie, movie_url)


# 3.保存数据
def save_movie(movie_url):

    # time.sleep(1)
    # 获取响应数据的过程是IO操作
    response = requests.get(movie_url)

    movie_dir = r'D:\项目路径\python13期\day30\梨视频'
    movie_path = os.path.join(
        movie_dir, str(uuid.uuid4()) + '.mp4'
    )
    # print(movie_path)
    with open(movie_path, 'wb') as f:
        for line in response.iter_content():
            f.write(line)


if __name__ == '__main__':
    response = get_page('https://www.pearvideo.com/')
    id_list = parse_page(response)
    for id_num in id_list:
        # 每一个视频详情页
        url = f'https://www.pearvideo.com/video_{id_num}'

        # 异步提交并爬取详情页任务
        # add_done_callback(parse_detail): 将get_page任务结束后的结果,扔给parse_detail函数
        # parse_detail函数接收的是一个对象,对象中的result()就是get_page函数的返回值。
        pool.submit(get_page, url).add_done_callback(parse_detail)

    import datetime

    print(datetime.datetime.now())
    # 21:54 ---> 18:45

 

TCP实现服务端并发

#server.py
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)
#client.py
import socket

client = socket.socket()

client.connect(
    ('127.0.0.1', 9000)
)

print('Client is run....')
while True:
    msg = input('客户端>>:').encode('utf-8')
    client.send(msg)

    data = client.recv(1024)
    print(data)

 

十二、回调函数

# add_done_callback
# from concurrent.futures import ThreadPoolExecutor
# import time
# # 池子对象: 内部可以帮你提交50个启动进程的任务
# p_pool = ThreadPoolExecutor(50)
#
#
# def task1(n):
#     print(f'from task1...{n}')
#     time.sleep(5)
#     return 'nana'
#
#
# def get_result(tank):
#     # print(obj.__dict__)
#     # print(obj._result)
#     result = obj.result()
#     print(result)
#
#
# if __name__ == '__main__':
#     n = 1
#     while True:
#         # 参数1: 函数名
#         # 参数2: 函数的参数1
#         # 参数3: 函数的参数2
#         # submit(参数1, 参数2, 参数3)
#         # add_done_callback(参数1),会将submit提交的task1执行的结果,传给get_result中的第一个参数,第一个参数是一个对象。
#         obj=p_pool.submit(task1, n)  obj.add_done_callback(get_result)
#         n += 1

 

posted @ 2019-12-07 19:09  小猪皮蛋  阅读(134)  评论(0编辑  收藏  举报