并发编程

进程概念

# 进程、线程都是操作系统中的基本概念,也就是说进程和线程都是操作系统层面的东西,专业术语表达就是进程和线程的使用都是有操作系统来调度的. 而不时有我们程序员自己来操控的

在操作系统这门课里面,进程和线程是操作系统的概念,协程不是操作系统中的概念,而是我们程序员层面的
协程是有我们程序员自己来调用的,不是有操作系统来调用的,也就是说,在操作系统中没有协程这个概念

进程是用来干什么的:
	进程可以用来做任务的,比如:听歌、看视频等

什么是进程?
	"""
		eg:
			厨师做饭,厨师按照菜谱来做菜,那么,菜谱就是程序,而做饭的过程就是进程,在这个过程中,厨师就是线程,所以,厨师才是真正做事的,言外之意线程才是真正干活的,而进程不是,进程只是一个过程
	"""

列如:电脑的任务管理器

进程和程序的区别:

1. 程序就是一堆死的东西,没有生命周期
: 进程是程序的一次执行过程,是动态概念。
: 程序是一组有序的指令集和,是静态概念。

2. 进程是有生命周期的,当一个任务进行完毕之后,进程就不存在了
: 当操作系统要完成某个任务时,它会创建一个进程。当进程完成任务之后,系统就会撤销这个进程,收回它所占用的资源。
从创建到撤销的时间段就是进程的生命期。

"""进程中是要有线程的,进程中如果没有线程是没有意义的,一个进程中是可以有多个线程的,一个进程中至少要有一个线程"""

进程和线程都是有操作系统来调度的
进程是操作系统的一个独立的单位

补充:CPU的工作机制
	1. 当CPU遇到I/O操作的时候,会剥夺CPU的执行权限
    2. 当遇到的任务需要占用大量的时间的时候,也会剥夺执行权限
    
"""其实CPU的工作机制是来回切换做到的"""

I/O密集型:
    input  output
   """遇到阻塞,但是不需要占用大量的CPU资源,需要等待,比如:sleep""" 	

计算密集型
	"""没有遇到阻塞,但是需要占用大量的CPU资源,也不需要等待"""
    

操作系统的调度算法(了解)

1. 先来先服务调度算法

: 它根据进程到达时间决定先运行哪一个进程,非抢占。

2. 短作业优先调度算法

: 是根据服务的时间经行选择。

3. 时间片轮转算法

: 当中断发生时,当前运行的程序置于就绪队列(队尾)中,然后基于FCFS选择下一个就绪作业运行。

4. 多级反馈队列

 : 在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业,将它们调入内存,
分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。

"""操作系统会自行选择哪一种算法"""

进程的并发和并行

1. 并行:在'同一时刻',同时执行任务


CPU是有多个核的,单核的,多核的
单核的CPU能不能做到并行?
做不到的

"""在同一时刻要想执行多个任务,必须要求CPU有多个处理器(核)"""

2. 并发:在一段时间内,看起来是同时执行任务,事实上,不是同一时刻

同步异步阻塞非阻塞

同步:依赖于上一次的结果


: 就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事

异步:不依赖于上一次的结果

: 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

例如 ajax请求(后面才学,也可以先了解一下)(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

# 异步的效率肯定是不同步的效率高

阻塞:

阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占cpu去执行其他逻辑,也会主动检测io是否准备好。

非阻塞:

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

再简单点理解就是:

1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。

2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)

3. 阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。

4. 非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者
 

如何开启进程

# 使用的是内置的模块:multiprocess
from multiprocessing import Process  # 从多进程里面加载一个单线程出来


def task():  # 定义一个task函数出来
    with open('a.txt', 'w', encoding="utf8") as f:  # 读文件
        f.write('helloworld')


# 开一个进程来执行task这个任务

# 如何开进程

"""在Windows系统中,开启进程必须写在__main__中, 在其他系统中不用加"""

if __name__ == '__main__':
    p = Process(target=task)  # task不加括号哦, 得到一个对象,还没有开启进程呢,知识实例化了一个进程对象

    p.start()  # 调用start这个方法才是真正的开启进程

Process类的参数:

def task(name, name1, age, gender):
    print(name)
    print(name1)
    print("age:", age)
    print("gender:", gender)
    # import time
    # time.sleep(5)
    # with open('a.txt', 'w', encoding="utf8") as f:
    #     f.write('helloworld')

# task('kevin')
# 开一个进程来执行task这个任务

# 如何开进程
"""在Windows系统中,开启进程必须写在__main__中, 在其他系统中不用加"""
if __name__ == '__main__':

    """
    group=None, target=None, name=None, args=(), kwargs={},
                 *, daemon=None
    
    每一个进程都有:进程名,进程id等属性
    name: 代表的是进程的名字
     args=(), 传参数的 
     kwargs={},
     daemon:守护进程
    """
    # p = Process(target=task)  # 得到一个对象,还没有开启进程呢,知识实例化了一个进程对象
    """这个开启进程只是通知操作系统去开进程,因为开启进程需要时间的,所以,在一瞬间进程并没有开起来,然后代码往下执行了"""
    # 开启的这个进程叫子进程
    p = Process(target=task, name='ly_1', args=('kevin', 'jason'), kwargs={'age':18, 'gender':'male'})  # 得到一个对象,还没有开启进程呢,知识实例化了一个进程对象
    # 守护进程的作用:主进程结束,子进程也结束
    # 注意:p.daemon = True 必须要写在
    p.daemon = True  # 把p进程设置为守护进程p.start()之前
    p.start()  # 调用这个方法才是真正的开启进程

    # 如何查看一个进程的名字
    # Process-1, ly_1
    # print(p.name)  # 看的是p进程的名字
    print("123")
    """现在的现象是:主进程代码先执行,然后执行子进程里的代码,并且主进程代码执行完毕之后,子进程的代码并没有结束"""

方法介绍

    # p.terminate()  # 杀死进程
    # # True
    # import time
    # time.sleep(1)
    #
    # # False
    # print(p.is_alive())  # 查看p进程是否存活

    # join
    # 现在让你先执行子进程中的代码,在执行主进程中的代码?
    p.join()  # 先等待子进程执行完毕,在执行主进程

如何开启多进程

import time


def task(i):
    print("task:", i)
    time.sleep(1)


from multiprocessing import Process

if __name__ == '__main__':
    start_time = time.time()

    ll = []  # 存的5个进程对象
    for i in range(5):  # 用for循环,可以同时开启5次
        # 开启多进程之后,有可能是没有顺序的
        p = Process(target=task, args=(i,))  # 循环一次,就打印一次i
        p.start()  # 谁先开启的就谁
        # p.join()  # 5个进程变成了串行  等这个今个进程执行完才进行下一个进程
        ll.append(p)  # 每开一个进程,都会把进程对象放在列表里面

    for j in ll:
        j.join()  # 每一个进程都加join
    """想让所有的子进程先执行完,在执行主进程"""
    print(123)
    print("time:", time.time() - start_time)
    # 打印顺序为:
    # task: 0
    # task: 1
    # task: 2
    # task: 3
    # task: 4
    # 123
    # time: 1.4832251071929932

进程锁

# 锁:以后你遇到的所有关于加锁的都是为了保证数据的安全

MySQL中:行锁、表锁、悲观锁、乐观锁、等
进程锁、互斥锁、GIL锁、死锁等

def task(i, lock):
    # 上一把锁
    lock.acquire()
    print("第%s个进程进来了" % i)
    print("第%s个进程走了" % i)
    # 释放锁
    lock.release()


from multiprocessing import Process
from multiprocessing import Lock
if __name__ == '__main__':
    lock = Lock()
    for i in range(5):
        p = Process(target=task, args=(i, lock))
        p.start()

进程间的通信(IPC机制)

# 进程是可以开启多个的,进程与进程之间是相互独立的,互补影响,进程是操作系统的一个独立单元
一个进程崩溃了,其他的进程不收影响

"""在一个进程中不能修改另外一个进程的数据,言外之意是进程之间的数据是隔离的,互补影响"""

# 要想在一个进程中使用另外一个进程中的数据,需要让进程之间通信(IPC)

实现高并发程序:

服务端:

import socket  # python提供的socket模块

def task(conn):
    while True:
        try:
            # 异常了一个bug,粘包现象
            data = conn.recv(1024)  # 括号里面写的是接收的字节数,最多接收1024个字节
            if len(data) == 0:
                continue
            print(data)  # 还是bytes类型

            # 服务端开始给客户端也发送一个数据
            conn.send(data.upper())
        except Exception as e:
            print(e)
            break

    conn.close()


from multiprocessing import Process   # 从多进程里面加载一个单线程出来

# 开启进程必须写在__main__中
if __name__ == '__main__':
    # 1. 买手机
    # SOCK_STREAM  ====> 代表的是TCP协议
    # socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
    server = socket.socket()  # 默认是TCP协议

    # 2. 买手机卡
    server.bind(('127.0.0.1', 8001))  # 服务端必须要有一个公网的IP所以绑定IP的信息在加上端口

    # 3. 开机
    server.listen(1)  # 监听,半连接池,等着客户端发消息
    print('服务端正在准备接收客户端消息:')
    while True:
        # 接收,程序启动之后,会在accept这里夯住,阻塞直到有客户端连接,accept并接收发来的消息
        conn, client_addr = server.accept()
        # # 开启的这个进程叫子进程
        p = Process(target=task, args=(conn,))
        # 调用start这个方法才是真正的开启进程
        p.start()

客户端:

import socket

# 先有一个手机
client = socket.socket()  # 先实例化socket对象

# 直接进行链接服务端
client.connect(('127.0.0.1', 8001))  # 链接服务端IP,建立连接

while True:
    # 让用户输入要发送的数据
    input_data = input('请输入你要发送的数据:')

    # 向服务端主动发送数据
    client.send(input_data.encode('utf-8'))  # 发送的数据必须是二进制,bytes类型

    # 接收服务端发送过来的数据
    server_data = client.recv(1024)  # 接收的最大字节数
    print(server_data)

client.close()
# 结束连接


客户端1:

import socket

# 先有一个手机
client = socket.socket() # 先实例化socket对象

# 直接进行链接服务端
client.connect(('127.0.0.1', 8001))   # 链接服务端IP,建立连接

while True:
    # 让用户输入要发送的数据
    input_data = input('请输入你要发送的数据:')

    # 向服务端主动发送数据
    client.send(input_data.encode('utf-8'))  # 发送的数据必须是二进制,bytes类型

    # 接收服务端发送过来的数据
    server_data = client.recv(1024)  # 接收的最大字节数
    print(server_data)

client.close()
# 结束连接

如何查看进程的id号

进程都有几个属性:进程名、进程id号(pid-->process id)
# 每一个进程都有一个唯一的id号, 通过这个id号就能找到这个进程。
# linux---->杀进程----->kill -9 1001----->pkill 进程名
import os
import time
def task():
    print("task中的子进程号:", os.getpid())
    print("主进程中的进程号:", os.getppid())  # parent
    time.sleep(20)

from multiprocessing import Process

if __name__ == '__main__':
    p = Process(target=task, )
    p.start()
    # 如何查看p进程的进程号
    print("子进程的进程号:", p.pid)  # 217164
    # 如何查看主进程的进程号:
    print("主进程的进程号:", os.getpid()) # os.getgid()这一句代码写在哪个进程下,输出的就是这个进程的进程号
    time.sleep(10)

队列的使用(Queue)

常见的数据结构:链表、单链表、双链表、循环链表、栈、队列、树、二叉树、平衡二叉树、红黑树、b树、b+树、b-树,图等

队列:先进先出
# 怎么样能够得到一个队列呢?
在python中,内置的有一个类,Queue
from multiprocessing import Queue

if __name__ == '__main__':
    q = Queue(3)  # 队列的大小默认很大
    # 1. 如何入队、
    """obj, block=True, timeout=None"""
    q.put('hellowrold1')
    q.put('helloworld2')
    q.put('helloworld3')
    # q.put('helloworld4', block=False)  # 如果你知道了block参数,当队列满的时候,放不进去的时候,直接报错
    # q.put('helloworld4', timeout=3)  # 如果你知道了timeout参数,当往队列中放入数据的时候,等待3秒,如果放不进去,直接报错
    # q.put_nowait('helloworld4')  # 往队列中放入数据的时候,放不进去直接报错, 指定了参数:block=False

    # 2. 出队
    # print(q.get())
    # print(q.get())
    print(q.get())
    """ block=True, timeout=None"""
    # print(q.get(block=False))
    # print(q.get(timeout=3))

    # q.get_nowait() #
    # print(q.qsize()) # 队列中剩余的数据量, 这个方法的结果有点不准确.
    # print(q.empty())
    # print(q.full())

解决进程之间的数据隔离问题

def task(q):
    a  = 1
    q.put("子进程写入的数据111")
    q.put(a)


from multiprocessing import Process,Queue

if __name__ == '__main__':
    q = Queue(3)
    p = Process(target=task,args=(q, ) )
    p.start()
    """
        我在子进程中放入一个数据,然后在主进程中取出这个数据,如果能去得到,就说明通信了,如果取不到就说明不通信
    """
    # 在主进程中取出子进程写进去的数据
    print(q.get())
    print(q.get())

    """现在队列中的数据在哪里存着? 在内存中存着,当数据量很大的时候,很占用我们机器的内存"""
    # 以后我们可能会使用专业的消息队列: kafka、rabbitmq、rocketmq等专业的消息队列
    """当前使用较多的消息队列有:RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等"""

生产者消费者模型

# 它不是只存在于Python中,在其他语言中也是有的,甚至于他跟语言就没有关系

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题,该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
# 版本4: 多生产者 少消费者的情况
def producer(q, name, food):
    """让生产者生产10个包子"""
    for i in range(10):
        """生产的包子放在哪里? 队列里"""
        q.put("生产者:%s,生产了第%s个 %s" % (name, i, food))
        import time
        time.sleep(0.1)


def consumer(q):
    while True:
        res = q.get()

        # if q.empty():
        #     break
        if res is None:
            break
        print(res)


from multiprocessing import Process, Queue

if __name__ == '__main__':
    q = Queue(20)

    # 4个生产者
    p1 = Process(target=producer, args=(q, 'kevin', '包子'))
    p2 = Process(target=producer, args=(q, 'jason', '豆浆'))
    p3 = Process(target=producer, args=(q, 'tank', '面包'))
    p4 = Process(target=producer, args=(q, 'oscar', '豆汁'))
    p1.start()
    p2.start()
    p3.start()
    p4.start()

    # 两个消费者
    p5 = Process(target=consumer, args=(q,))
    p6 = Process(target=consumer, args=(q,))
    p5.start()
    p6.start()

    p1.join()
    p2.join()
    p3.join()
    p4.join()
    """放的None的数量超过消费者的数量就可以正常结束程序"""
    q.put(None)
    q.put(None)

队列可能会出现什么问题,如何解决的?
# 队列积压问题,队列里面的数据不能够被及时的消费掉造成的问题
# 把队列中的数据消费掉,以后我们会有专业的队列工具,是一个网站,手动的处理

线程理论

进程是一个任务的执行过程,在这个过程中实际做事的是线程,线程是开在进程里面的,需要先有进程,在有线程,一个进程中至少有一个线程,当然,也可以有多个线程。

# 进程是资源分配的基本单位,线程是CPU执行的最小单位

进程和线程都是有操作系统来调度的,协程它是有程序员来调度的。

进程 >>> 线程 >>> 协程
资源消耗是最多的  >>>  线程的资源 >>> 协程的

"""我们学了进程和线程之后,以后到底是开多进程还是开多线程呢? """ GIL锁相关

如何开启线程

def task():
    print("task执行了")  # task执行了


from multiprocessing import Process
from threading import Thread

if __name__ == '__main__':
    t = Thread(target=task)
    t.start()
    
from multiprocessing import Process
from threading import Thread
import threading

if __name__ == '__main__':
    """
        target=None, name=None,
                 args=(), kwargs=None, *, daemon=None
                 
        name='ly_1': 修改线程名称
        
    """
    t = Thread(target=task, name='ly_1', args=('kevin', 'jason' ), kwargs={'age':18})
    # t.daemon = True
    # t.setDaemon(True)  # 守护线程:主线程结束,子线程跟着结束
    t.start()
     
    # print(t.name)  # Thread-1
    # print(123)
    '''
     Thread类的其他方法
     Thread实例对象的方法:
     ● isAlive():返回线程是否活动的。
     ● getName():返回线程名。
     ● setName():设置线程名。
     threading模块提供的一些方法:
     ● threading.currentThread():返回当前的线程变量。
     ● threading.enumerate():返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
     ● threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
    '''
 
    # print(t.isAlive())
    # print(t.is_alive())
    # print(t.getName())
    # print(t.name)
    # t.setName('ly_2')
    # print(t.getName())
    print(threading.currentThread())  # <_MainThread(MainThread, started 110848)>

如何开启多线程

def task(i):
    print(i)


from threading import Thread
if __name__ == '__main__':
    tt = []
    for i in range(5):
        t = Thread(target=task, args=(i, ))
        t.start()
        tt.append(t)
    for j in tt:
        j.join()
        
    print("123")

进程和线程的比较

1. 进程的开销远远大于线程的开销
2. 进程之间的数据是隔离的,线程之间的数据呢? 线程之间的数据是共享的,严格的来说:同一个进程下的线程之间的数据是共享的
3. 想让不同进程之间的线程之间的数据共享------->还是让进程之间通信------->线程之间也通信了--->队列
posted @ 2023-07-05 21:01  毓见  阅读(48)  评论(0)    收藏  举报