python学习生涯 day24-29

这篇博客讲解的是关于进程和线程相关的知识点

0 进程

什么是进程?
进程(process)
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元

进程调度的几种方式
0)先来先服务(FCFS)
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)
1)短作业优先调度算法
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
2)时间片轮转法
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
在轮转法中,加入到就绪队列的进程有3种情况:
一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
第三种情况就是新创建进程进入就绪队列。
如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。
3)多级反馈队列
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。

1 进程的并行和并发:

并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
区别:
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。

2 同步异步阻塞非阻塞:

进程三态状态转换图(就绪,运行,堵塞)在这里插入图片描述
一个实例说明:在这里插入图片描述
同步/异步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

做一个比较恰当的比喻:

比如我去银行办理业务,可能会有两种方式:
第一种 :选择排队等候;
第二种 :选择取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了;

第一种:前者(排队等候)就是同步等待消息通知,也就是我要一直在等待银行办理业务情况;

第二种:后者(等待别人通知)就是异步等待消息通知。在异步消息处理中,等待消息通知者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的号码,喊号)找到等待该事件的人。

堵塞/非堵塞

继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。

注意:同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有。如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。

3 进程的两种启动方式

0)不使用类

import os
import time
from multiprocessing import Process


def func(*args):
    print(args)
    print(12345)
    time.sleep(1)
    print('子进程:', os.getpid())
    print(54321)
    print('子进程的父进程:', os.getppid())


if __name__ == '__main__':
    # 注册
    p = Process(target=func, args=('参数1', '参数2'))
    # 启动
    p.start()
    print('*'*10)
    print('父进程:', os.getpid())
    print('父进程的父进程:', os.getppid())

在这里插入图片描述

import os
import time
from multiprocessing import Process


def func(*args):
    print('*'*args[0])
    time.sleep(5)
    print('*'*args[1])


if __name__ == '__main__':
    # 注册
    p = Process(target=func, args=(10, 20))
    # 启动
    print('我是异步的哦')
    p.start()
    p.join()  # 感知一个子进程的结束,将异步的程序改为同步
    print('----------: 运行结束')
import os
from multiprocessing import Process


def func(filename, content):
    with open(filename, 'w') as f:
        f.write(content*'*')


if __name__ == '__main__':
    # 注册
    p_list = []
    for i in range(5):
        p = Process(target=func, args=('info%s' % i, i))
        p_list.append(p)
        p.start()
    [p.join() for p in p_list]  # 所有的子进程都必须执行完才能执行下面的操作
    #  os.walk(path)是返回该路径下的所有文件
    print([i for i in os.walk('/Users/gaoye/PycharmProjects/multiprocessing')])

在这里插入图片描述

1)使用类

import os
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self, args1, args2):
        # 写父类 一定不要忘记写 super()
        super().__init__()
        self.args1 = args1
        self.args2 = args2

    def run(self):
        print(os.getpid())
        print(self.name)
        print(self.pid)


if __name__ == '__main__':
    print('主pid:', os.getpid()) # 不知道怎么传参数,就写一个__init__()
    myprocess = MyProcess(1, 2)
    myprocess.start()
    myprocess1 = MyProcess(3, 4)
    myprocess1.start()

在这里插入图片描述
注意:几个进程之间,如果不通过特殊的手段,他们之间的数据是不共享的

4 一个支持多进程的tcp服务

# server端
import socket
from multiprocessing import Process


def server(conn):
        conn.send(bytes('你好:', encoding='utf-8'))
        msg = conn.recv(1024).decode('utf-8')
        print(msg)
        conn.close()


if __name__ == '__main__':
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8090))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        p = Process(target=server, args=(conn,))
        p.start()
    sk.close()
# client端
import socket
from multiprocessing import Process

sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
msg = sk.recv(1024).decode('utf-8')
print(msg)
inf = input('>>>').encode('utf-8')
sk.send(inf)
sk.close()

5 守护进程

from multiprocessing import Process
import time


def func():
    while True:
        time.sleep(0.5)
        print('我还活着呢')


def func2():
    time.sleep(12)
    print('给👴死')


if __name__ == '__main__':
    p = Process(target=func)
    # 守护进程
    p.daemon = True
    p.start()
    Process(target=func2).start()
    i = 0
    while i < 10:
        print('我是牛鬼蛇神')
        time.sleep(1)
        i += 1
        

注意; 当主进程中的代码执行结束后,守护进程自动去世
多进程的其他方法(终止,判断死活);

from multiprocessing import Process
import time


def func():
    print('我还活着呢')


def func2():
    print('给👴死')


if __name__ == '__main__':
    p = Process(target=func)
    # 守护进程
    p.daemon = True
    p.start()
    p2 = Process(target=func2)
    p2.start()
    # 终止一个进程,但这个过程需要时间, 所以一般在接下来的print(p.is_alive())依然是True
    time.sleep(1)
    p.terminate()
    print(p.is_alive())
    # 经过2秒后,已经执行完了terminate的操作
    time.sleep(2)
    print(p.is_alive())

在这里插入图片描述

6 进程的同步控制(锁/信号量/事件):

0) 进程锁

from multiprocessing import Lock
from multiprocessing import Process
import json
import time


def show(i):
    with open('ticket.txt', 'r') as f:
        dic = json.load(f)
    print('剩余票数:%s'% dic['ticket'])


def buy_ticket(i, lock):
    # 拿钥匙进门
    lock.acquire()
    with open('ticket.txt', 'r') as f:
        dic = json.load(f)
        time.sleep(0.1)
    if dic['ticket'] > 0:
        dic['ticket'] -= 1
        print('%s买到票啦!!!'% i)
    else:
        print('黄牛抢走了你的票!!!')
    time.sleep(0.1)
    with open('ticket.txt', 'w') as f:
        json.dump(dic, f)
    # 还钥匙
    lock.release()


if __name__ == '__main__':
    for i in range(10):
        # args在传参数的时候,以元组的方式传入
        p = Process(target=show, args=(i,))
        p.start()
    lock = Lock()
    for i in range(10):
        p1 = Process(target=buy_ticket, args=(i, lock))
        p1.start()

在这里插入图片描述
注意:一般涉及到改的过程都需要加锁

1)信号量(可以理解成自定义个数的锁)

from multiprocessing import Process
from multiprocessing import Semaphore
import random
import time


def ktv(i, sem):
    sem.acquire()
    print('%s走进了ktv' % i)
    time.sleep(random.randint(1, 5))
    print('%s走出了ktv' % i)
    sem.release()


if __name__ == '__main__':
    sem = Semaphore(4)
    for i in range(20):
        p = Process(target=ktv, args=(i, sem))
        p.start()

在这里插入图片描述
2)事件

from multiprocessing import Event


e = Event()
print(e.is_set())  # False
e.set()
print(e.is_set())  # True
e.wait()
print('12345')
e.clear()          # False
e.wait()
print('78900')

在这里插入图片描述
总结:
set和clear:分别用来修改一个事件的状态 True/False
is_set: 用来查看一个事件的状态
wait :依据事件的状态来决定是否堵塞 False堵塞 True不堵塞

7 一个有关红绿灯的程序(多进程):

from multiprocessing import Event
from multiprocessing import Process
import time
import random


def light(e):
    while True:
        if e.is_set():
            print('\033[32m绿灯亮\033[0m')
            time.sleep(2)
            e.clear()

        else:
            print('\033[31m红灯亮\033[0m')
            time.sleep(2)
            e.set()


def cars(e, i):
    if not e.is_set():
        print('第%s辆车正在等待' % i)
        e.wait()
    print('第%s辆车已通行' % i)


if __name__ == '__main__':
    e = Event()
    p = Process(target=light, args=(e, ))
    p.start()
    for i in range(20):
        car = Process(target=cars, args=(e,i,))
        car.start()
        time.sleep(random.random())

8 IPC(inter processing commuication)进程间通信

0) 队列

from multiprocessing import Queue


q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
q.put(5)
# 查看队列是否满了
print(q.full())
q.get()
q.get()
q.get()
q.get()
print(q.get())
# 查看队列是否空了
print(q.empty())

在这里插入图片描述

from multiprocessing import Process
from multiprocessing import Queue


def producer(q):
    q.put('hello world')


def consumer(q):
    print(q.get())


if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer, args=(q,))
    p.start()
    p = Process(target=consumer, args=(q,))
    p.start()

在这里插入图片描述

生产者消费者模型

# -*- coding:utf-8 -*-
from multiprocessing import Process
from multiprocessing import Queue
import time
import random


def producer(name, food, q):
    for i in range(4):
        time.sleep(random.randint(1, 5))
        foods = '%s生产了第%s的%s' % (name, i, food)
        print(foods)
        q.put(foods)


def consumer(q, name):
    while True:
        time.sleep(random.randint(1, 3))
        food = q.get()
        if food is None:
            break
        print('%s吃掉了%s' % (name, food))


if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer, args=('jinx', '包子', q))
    p2 = Process(target=producer, args=('vn', '馒头', q))
    c1 = Process(target=consumer, args=(q, 'jax'))
    c2 = Process(target=consumer, args=(q, 'jin'))
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    # 主进程等待子进程结束才会执行后面的内容, 如果只设置一个None,其中一个子进程就不会结束(感知一个进程的结束)
    p1.join()
    p2.join()
    q.put(None)
    q.put(None)

在这里插入图片描述

生产者消费者模型(JoinableQueue)

import time
import random
from multiprocessing import Process
from multiprocessing import JoinableQueue


def consumer(q, name):
    while True:
        food = q.get()
        print('\033[32m%s消费了%s\033[0m' % (name, food))
        time.sleep(random.randint(1, 3))
        # count - 1
        q.task_done()


def producer(name, food, q):
    for i in range(4):
        time.sleep(random.randint(1, 3))
        f = '%s生产了%s%s' % (name, food, i)
        print(f)
        q.put(f)
    # 堵塞,直到队列中的所有数据都被处理完毕
    q.join()


if __name__ == '__main__':
    q = JoinableQueue(20)
    p1 = Process(target=producer, args=('jinx', '包子', q))
    p2 = Process(target=producer, args=('jax', '馒头', q))
    c1 = Process(target=consumer, args=(q, 'lux'))
    c2 = Process(target=consumer, args=(q, 'vls'))
    p1.start()
    p2.start()
    # 将两个消费者进程设置为守护进程,当主进程结束时,两个消费者进程自动结束
    c1.daemon = True
    c2.daemon = True
    c1.start()
    c2.start()
    p1.join()
    p2.join()

小结:
consumer中把所有的任务消耗完
producer端的join感知到,停止堵塞
所有的producer进程结束
主进程的p.join结束
守护进程(消费者进程)结束
在这里插入图片描述
1) 管道(Pipe)

from multiprocessing import Process
from multiprocessing import Pipe


def func(conn1):
    conn1.send('你吃了吗')


if __name__ == '__main__':
    conn1, conn2 = Pipe()
    Process(target=func, args=(conn1,)).start()
    print(conn2.recv())

在这里插入图片描述
可以实现两个进程之间的通信

from multiprocessing import Process
from multiprocessing import Pipe


def func(conn1, conn2):
    conn2.close()
    while True:
        try:
            msg = conn1.recv()
            print(msg)
        except EOFError:
            conn1.close()
            break


if __name__ == '__main__':
    conn1, conn2 = Pipe()
    Process(target=func, args=(conn1, conn2)).start()
    conn1.close()
    for i in range(20):
        conn2.send('你吃了吗???')
    conn2.close()

在这里插入图片描述

在这里插入图片描述
管道内没有其他的send和recv时,就会报错(EOFError)

生产者消费者模型(Pipe)

import time
import random
from multiprocessing import Process
from multiprocessing import Pipe


def producer(pro, con, name, food):
    con.close()
    for i in range(10):
        f = '\033[32m%s生产了%s' % (name, food)
        print(f)
        pro.send(f)
        time.sleep(random.randint(1, 3))
    pro.close()


def consumer(pro, con, name):
    pro.close()
    while True:
        try:
            food = con.recv()
            print('%s吃掉了%s' % (name, food))
            time.sleep(random.randint(1, 3))
        except EOFError:
            con.close()
            break


if __name__ == '__main__':
    pro, con = Pipe()
    Process(target=producer, args=(pro, con, 'jinx', '橘子')).start()
    Process(target=consumer, args=(pro, con, 'vls')).start()
    pro.close()
    con.close()

在这里插入图片描述
注意: pipe是数据不安全的,所以了解即可,一般都是用队列

9 进程之间数据共享(Manager)

# 虽然可以用队列和管道来实现,但是一般都是使用manage方法
from multiprocessing import Manager
from multiprocessing import Process


def main(dic):
    dic['nums'] -= 1


if __name__ == '__main__':
    m = Manager()
    dic = m.dict({'nums': 100})
    i_list = []
    for i in range(100):
        p = Process(target=main, args=(dic,))
        p.start()
        i_list.append(i)
    for i in i_list:
        p.join()
    print('主进程', dic['nums'])

在这里插入图片描述
说明:这种情况也是由于数据不安全导致的,怎么解决数据不安全呢,加锁,加就完事了

from multiprocessing import Manager
from multiprocessing import Process
from multiprocessing import Lock


def main(dic, lock):
    lock.acquire()
    dic['nums'] -= 1
    lock.release()


if __name__ == '__main__':
    m = Manager()
    lock = Lock()
    dic = m.dict({'nums': 100})
    i_list = []
    for i in range(100):
        p = Process(target=main, args=(dic, lock))
        p.start()
        i_list.append(p)
    for i in i_list:
        p.join()
    print('主进程', dic['nums'])

在这里插入图片描述

10 进程池

使用进程池开进程,比不使用进程池开进程,效率高很多

from multiprocessing import Process
from multiprocessing import Pool
import time


def func(n):
    for i in range(10):
        print(n+1)


if __name__ == '__main__':
    start = time.time()
    p = Pool(5)
    p.map(func, range(100))
    t1 = time.time() - start
    start = time.time()
    l_list = []
    for i in range(100):
        p = Process(target=func, args=(i, ))
        p.start()
        l_list.append(Process(target=func, args=(i, )))
    for i in l_list:
        p.join()
    t2 = time.time() - start
    print(t1, t2)

使用进程池开进程和不使用进程池开进程的时间对比:
在这里插入图片描述
进程池的几种方法:
0)apply()

import os
import time
from multiprocessing import Pool


def func(n):
    print('strat in func %s' % n, os.getpid())
    time.sleep(1)
    print('end in func %s' % n, os.getpid())


if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        p.apply(func, args=(i,))

在这里插入图片描述
其中,进程池.apply为同步

1) apply_async()

import os
import time
from multiprocessing import Pool


def func(n):
    print('strat in func %s' % n, os.getpid())
    time.sleep(1)
    print('end in func %s' % n, os.getpid())


if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        p.apply_async(func, args=(i,))
    p.close()  # 结束进程池接收任务
    p.join()  # 感知进程池中的任务执行结束

在这里插入图片描述
其中,进程池.async为异步。不过比起同步,需要加两行代码

把进程池和tcp结合起来

# server端
import socket
from multiprocessing import Pool


def func(conn):
        conn.send(b'hello')
        print(conn.recv(1024).decode('utf-8'))
        conn.close()


if __name__ == '__main__':
    p = Pool(5)
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8090))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        p.apply_async(func, args=(conn,))
    sk.close()
# client端
import socket


sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
print(sk.recv(1024).decode('utf-8'))
msg = input('>>>').encode('utf-8')
sk.send(msg)
sk.close()

2)map方法:

import time
from multiprocessing import Pool


def func(i):
    time.sleep(0.5)
    return i*i


if __name__ == '__main__':
    p = Pool(5)
    ret = p.map(func, range(10))
    print(ret)

在这里插入图片描述
小结:
p = Pool()
p.map(funcname, iterable) # 默认异步执行任务,且自带close和join功能
p.apply 同步调用的
p.apply_async 异步调用,且与主进程完全异步 需要手动close和join

11 进程池的回调函数

import os
from multiprocessing import Pool


def func1(n):
    print('in func1', os.getpid())
    return n*n


def func2(n):
    print('in func2', os.getpid())
    print(n)


if __name__ == '__main__':
    p = Pool(5)
    print('主进程:', os.getpid())
    for i in range(10):
        p.apply_async(func1, args=(i,), callback=func2)
    p.close()
    p.join()

在这里插入图片描述

一个使用了回调函数的小爬虫

from multiprocessing import Pool
import requests


def get(url):
    response = requests.get(url)
    if response.status_code == 200:
        return url, response.content.decode('utf-8')


def call_back(args):
    url, content = args
    print(url, len(content))


if __name__ == '__main__':
    url_list = [
        'https://www.cnblogs.com/',
        'http://www.baidu.com',
        'https://www.sogou.com/',
        'http://www.sohu.com/',
        'https://www.bilibili.com/video/BV1Et411f7fD?p=182'
    ]
    p = Pool(5)
    for url in url_list:
        p.apply_async(get, args=(url,), callback=call_back)
    p.close()
    p.join()

在这里插入图片描述

12 线程的两种启动方式

0)不使用类

import time
from threading import Thread
from multiprocessing import Process


def func(n):
    time.sleep(2)
    print(n)


if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=func, args=(i,)).start()

1) 使用类

import time
from threading import Thread
from multiprocessing import Process


class MyThread(Thread):
    def run(self):
        time.sleep(2)
        print(1)


class MyProcess(Process):
    def run(self):
        print(2)


if __name__ == '__main__':
    t = MyThread()
    t.start()
    p = MyProcess()
    p.start()

在这里插入图片描述
注意:与进程的类的启动方式相似,不过类中的方法名必须是run才行,经过查看Thread这个源代码,发现里面有一个run方法
在这里插入图片描述
证明在同一个进程里面执行

import os
import time
from threading import Thread
from multiprocessing import Process


class MyThread(Thread):
    def run(self):
        time.sleep(2)
        print(1)
        print(os.getpid())


class MyProcess(Process):
    def run(self):
        print(2)
    

if __name__ == '__main__':
    t = MyThread()
    t.start()
    p = MyProcess()
    p.start()
    print('主进程:', os.getpid())

在这里插入图片描述

13 多线程之间共享数据

import os
from threading import Thread


g = 100


def func(a, b):
    global g
    g = 0
    print(g, os.getpid())


t_list = []
for i in range(10):
    t = Thread(target=func, args=(i, 5))
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()
print(g)

在这里插入图片描述
注意:但是线程之间是数据不安全的,这也是它的最大弊端

14 全局解释器锁

在这里插入图片描述
在这里插入图片描述
小结:
进程是最小的内存分配单位 线程是操作系统调度的最小单位
Cpu真正执行的是线程
进程内至少含有一个线程
开启一个线程所需要的时间要远远小于开启一个进程的时间
多个进程内部都有自己的数据站,数据之间不共享
全局变量在多个线程之间的共享的
在Cpython解释器下的python程序,在同一时刻,多个线程之间只有一个会被cpu执行
高cpu:计算类——高cpu利用率
高io:爬取网页 qq聊天 读写数据库

15 进程和线程的效率对比

import time
from multiprocessing import Process
from threading import Thread


def func(n):
    n + 1


if __name__ == '__main__':
    start = time.time()
    p_list = []
    for i in range(100):
        p = Process(target=func, args=(i,))
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    t1 = time.time() - start
    start = time.time()
    p_list = []
    for i in range(100):
        p = Thread(target=func, args=(i,))
        p.start()
        p_list.append(p)
    for p in p_list:
        p.join()
    t2 = time.time() - start
    print(t1, t2)

在这里插入图片描述

16 一个支持多线程的tcp服务

# server端
import socket
from threading import Thread


def run(conn):
    conn.send(b'hello')
    msg = conn.recv(1024).decode('utf-8')
    print(msg)
    conn.close()


    sk = socket.socket()
    sk.bind(('127.0.0.1', 8090))
    sk.listen()
    while True:
        conn, addr = sk.accept()
        Thread(target=run, args=(conn,)).start()
    sk.close()
# client端
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
while True:
    print(sk.recv(1024).decode('utf-8'))
    sk.send(input('>>>').encode('utf-8'))
sk.close()

17 多线程的其他方法

import time
import threading


def func(n):
    time.sleep(0.5)
    print(n, threading.current_thread(), threading.get_ident())


for i in range(10):
    threading.Thread(target=func, args=(i,)).start()
print(threading.active_count())
print(threading.current_thread())
print(threading.enumerate())

在这里插入图片描述

18 守护线程

import time
from threading import Thread


def func1():
    print('in func1')
    time.sleep(10)


def func2():
    print('in func2')
    time.sleep(5)


t1 = Thread(target=func1)
t1.daemon = True
t1.start()
t2 = Thread(target=func2)
t2.start()
print('主线程执行完毕!')

在这里插入图片描述
注意:
守护进程会随着主进程的代码执行结束后结束
守护线程会随着主进程的代码和其他线程的代码执行结束后结束
主进程在执行完自己的代码后不会立即结束,而是等待子进程结束后,回收子进程的资源

19 线程的同步控制(锁/信号量/事件):

0)死锁:

import time
from threading import Thread, Lock

noodle_lock = Lock()
fork_lock = Lock()


def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    print('%s开吃!!!' % name)
    fork_lock.release()
    noodle_lock.release()


def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    print('%s开吃!!!' % name)
    noodle_lock.release()
    fork_lock.release()


Thread(target=eat1, args=('jinx',)).start()
Thread(target=eat2, args=('jin',)).start()
Thread(target=eat1, args=('jax',)).start()
Thread(target=eat2, args=('lux',)).start()

在这里插入图片描述
其实,死锁的问题多进程也存在,在这里一并说明

1)递归锁,为了解决死锁的问题

import time
from threading import Thread, RLock


noodle_lock = fork_lock = RLock() # 可以吧RLock()理解成一串钥匙,其中的noodle_lock和fork_lock都是一把钥匙


def eat1(name):
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    print('%s开吃!!!' % name)
    fork_lock.release()
    noodle_lock.release()


def eat2(name):
    fork_lock.acquire()
    print('%s拿到叉子了' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s拿到面条了' % name)
    print('%s开吃!!!' % name)
    noodle_lock.release()
    fork_lock.release()


Thread(target=eat1, args=('jinx',)).start()
Thread(target=eat2, args=('jin',)).start()
Thread(target=eat1, args=('jax',)).start()
Thread(target=eat2, args=('lux',)).start()

在这里插入图片描述
2) 信号量

import time
from threading import Thread, Semaphore


def func(sem, a, b):
    sem.acquire()
    print(a+b)
    time.sleep(1)
    sem.release()


sem = Semaphore(4)
for i in range(10):
    Thread(target=func, args=(sem, i, i+5)).start()

在这里插入图片描述
说明: 4个4个出结果
3)事件(这里用红绿灯案例进行说明)

import time
import random
from threading import Thread, Event


def light(e):
    while True:
        if e.is_set():
            print('\033[32m绿灯亮\033[0m')
            time.sleep(2)
            e.clear()
        else:
            print('\033[31m红灯亮\033[0m')
            time.sleep(2)
            e.set()


def cars(e, i):
    if not e.is_set():
        print('第%s辆车正在等待' % i)
        e.wait()
    print('第%s辆车已通行' % i)


e = Event()
Thread(target=light, args=(e,)).start()
for i in range(20):
    Thread(target=cars, args=(e, i)).start()
    time.sleep(random.random())

在这里插入图片描述

连接数据库案例

import time
import random
from threading import Thread, Event


def connect_db(e):
    count = 0
    while count < 3:
        e.wait(0.5)
        if e.is_set() == True:
            print('连接成功!!!')
            break
        else:
            count += 1
            print('第%s次连接失败' % count)
    else:
        raise TimeoutError('数据库连接超时!!')


def check_web(e):
    time.sleep(random.randint(0, 3))
    e.set()

e = Event()
Thread(target=connect_db, args=(e,)).start()
Thread(target=check_web, args=(e,)).start()

在这里插入图片描述

20 线程的条件(Condition)

from threading import Thread, Condition


def func(con, i):
    con.acquire()
    con.wait()   # 等钥匙
    print('在第%s个循环里' % i)
    con.release()


con = Condition()
for i in range(10):
    Thread(target=func, args=(con, i)).start()
while True:
    num = int(input(">>>"))
    con.acquire()
    con.notify(num)   # 造钥匙
    con.release()

在这里插入图片描述
说明:Condition一共提供了4种方法: acquire, release,wait和notify

21 定时器(Timer)

import time
from threading import Timer


def func():
    print('时间同步')


while True:
    Timer(5, func).start()   # 非堵塞的
    time.sleep(5)

在这里插入图片描述

22 队列(queue)

import queue

q = queue.Queue()   # 普通队列
q.put(1)
q.put(2)
q.put(3)
print(q.get())
q1 = queue.LifoQueue()   # 栈
q1.put(1)
q1.put(2)
q1.put(3)
print(q1.get())
q2 = queue.PriorityQueue()   # 优先级队列,当优先级相同时,看对应的ACSII码
q2.put((1, 'a'))
q2.put((2, 'a'))
q2.put((3, 'a'))
q2.put((1, 'b'))
print(q2.get())

在这里插入图片描述

23 线程池

import time
from concurrent.futures import ThreadPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


tpool = ThreadPoolExecutor(max_workers=5)  # 一般情况下不超过cpu数目乘5
t_list = []
for i in range(20):
    t = tpool.submit(func, i)
    t_list.append(t)

tpool.shutdown()  # close + join
print('主线程')
for t in t_list:
    print('***', t.result())  # result方法得到返回值

在这里插入图片描述
对于进程池而言,除了之前学过的multiprocessing中的Pool 也可以使用该模块下的方法来实现

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


if __name__ == '__main__':
    tpool = ProcessPoolExecutor(max_workers=5)  # 一般情况下不超过cpu数目乘5
    t_list = []
    for i in range(20):
        t = tpool.submit(func, i)
        t_list.append(t)

    tpool.shutdown()  # close + join
    print('主线程')
    for t in t_list:
        print('***', t.result())  # result方法得到返回值

Map方法

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


tpool = ThreadPoolExecutor(max_workers=5)  # 一般情况下不超过cpu数目乘5
tpool.map(func, range(20))

使用map方法不能拿到返回值

24 线程池下的回调函数

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def func(n):
    time.sleep(2)
    print(n)
    return n*n


def feed_back(n):
    print('结果是 %s' % n.result())


tpool = ThreadPoolExecutor(max_workers=5)  # 一般情况下不超过cpu数目乘5
for i in range(20):
    tpool.submit(func, i).add_done_callback(feed_back)  # 进程池下的回调函数

在这里插入图片描述

特别说明;经过30天的学习,本人发现python是个坑,本人已经跑路了,下次更新java的知识点,不见不散!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值