23 并发编程 03

并发编程

昨日内容回顾

进程对象属性及其他方法

from multiprocessing import Process,current_process
import os
current_process().pid   #查看当前进程号
os.getpid() #查看当前进程号
os.getppid()#查看当前进程的父进程号
windows中终端命令
tasklist
tasklist|findstr pid

僵死进程与孤儿进程

僵死进程
孤儿进程

守护进程

被守护进程结束后守护进程立刻也跟着结束
如何开启:在start之前写p.daemon=True
                     p.start()

互斥锁

"""
多个人在操作同一份数据的时候,可能会出现数据错乱的问题
针对上述问题我们正常都是加锁处理
作用:将并发变成串行,牺牲了程序运行的效率但是保证了数据的安全
注意:只在操作数据的部分加锁即可
锁尽量不要自己去处理,很容易造成死锁现象
"""
扩展:行锁,表锁
#抢锁
q.acquire()
#释放锁
q.release()
#模拟抢锁

队列Queue

队列:先进先出
堆栈:先进后出
from multiprocessing import Queue
q=Queue()
q.put()
q.get()
q.full()
q.empty()
q.get_nowait()#取数据的时候如果没有数据直接报错
q.get(timeout=5)

进程间通信

进程间无法直接进行数据交互,但是可以通过队列或者管道实现数据交互
本地测试的时候可能会用到Queue,实际生产用的都是别人封装好的功能非常强大的工具

生产者与消费者模型

生产者+消息队列+消费者
为何要有消息队列,是为了解决供需不平衡的问题
#joinableQueue
可以被等待的q
你在往队列中放数据的时候,内部会有一个计数器自动加1
你在往队列中放数据的时候,调用task_done(),内部计数器自动减1
q.join()  当计数器为0的时候才继续往下运行

线程理论

进程:资源单位
线程:执行单位。线程是真正干活的人,干活的过程中所需的资源由线程所在的进程提供
每一个进程肯定都自带一个线程
同一个进程内可以创建多个线程
开进程:申请内存空间,拷贝代码,消耗资源较大
开线程:同一个进程内创建多个线程,无需上述2个步骤,消耗资源相对较小

今日内容

开启线程的2种方式

#开启线程不需要在main下面执行代码,直接书写就可以
#但是我们习惯性的将启动写在main下
#针对两个双下划线开头双下划线开头的方法,读成双下init
from multiprocessing import Process
from threading import Thread
def task():
    print("子线程")
if __name__ == '__main__':
    t = Process(target=task)
    t.start()
    print("主线程")
# from multiprocessing import Process
# from threading import Thread
# def task():
#     print("子线程")
# if __name__ == '__main__':
#     t=Thread(target=task)
#     t.start()
#     print("主线程")

如何实现tcp服务端并发的效果

import socket
from threading import Thrread
from multiprocess import Process
#服务端要有固定的端口 24小时不间断提供服务 能够支持并发
server=socket.socket() #括号内不加参数默认是tcp协议
server.bind(("127.0.0.1",8080))
server.listen(5)
#链接循环
def talk(conn):
    while True:
        try:
            data=conn.recv(1024)
            if len(data)==0:break
            print(data.decode("utf-8"))
            conn.send(data.upper())
        except ConnectionResetError as e
            print(e)
            break
     conn,close()
while True:
    conn,addr=server.accept()
    t=Thread(target=talk,args=(conn,))
    t.start()
 #客户端
import socket
client=socket.socket()
client.connect(("127.0.0.1",8080))
while True:
    client.send(b"hello")
    data=client.recv(1024)
    print(data.decode("utf-8"))
        

线程对象的join方法

t.join  同进程

同一个进程下的多个线程数据共享

from threading import Thread
import time
money=100
def task():
    global money
    money=666
if __name__ == '__main__':
    t=Thread(target=task)
    t.start()
    print(money)

线程对象及其他方法

rom threading import Thread,active_count,current_thread
import time,os

def task():
    #print("hello",os.getpid())
    print("hello",current_thread().name)


if __name__ == '__main__':
    t=Thread(target=task)
    t.start()
    print("主",active_count())
    #print("主",os.getpid())
    print("主",current_thread().name)

守护线程

主线程运行结束之后不会立即结束,会等待其他非守护线程结束才结束
因为主线程的结束意味着所在进程的结束

t.setDaemon()

线程互斥锁

from threading import Thread,Lock
import time
money=100
lock=Lock()
def task():
    global money
    lock.acquire()
    tmp=money
    time.sleep(0.1)
    money=tmp-1
    lock.release()
if __name__ == '__main__':

    t_list=[]
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)

GIL全局解释器锁

python解释器有多个版本,cpython,pypypython,但普遍使用的是cpython
在cpython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行
同一进程下的多个线程无法利用多核优势
因为cpython中的内存管理不是线程安全的
内存管理(垃圾回收机制)
1.引用计数
2.标记清楚
3.分代回收
重点:1.GIL不是python的特点,而是cpython解释器的特点
      2.GIL是保证解释器级别的数据安全
      3.同一进程下的多个线程无法同时进行 ,即无法利用多核优势
      4.针对不同的数据还是需要加不同的锁处理
    5.解释型语言的通病同意进程下多个线程无法利用多核优势
    

GIL与普通锁的区别


同一个进程下的多线程无法利用多核优势,是不是就没有用了

多线程分单核多核

计算密集型:单核:多进程:额外的消耗资源

​ 多线程:

​ 多核:多进程:总耗时10+

​ 多线程:总耗时40+

I/O密集型:多核:多进程:相对资源浪费

​ 多线程:更加节省资源

### 死锁与递归锁

死锁案列
import time

from threading import Thread,Lock,RLock
lock1 = Lock()
lock2 = Lock()
# lock1 = RLock()  # 递归锁, 可重入锁
# lock2 = RLock()

def eat1( name):
    lock1.acquire()
    print("%s抢到了面条" % name)
    time.sleep(1)
    lock2.acquire()
    print("%s抢到了筷子" % name)
    time.sleep(1)
    lock2.release()
    print("%s放下了筷子" % name)
    time.sleep(1)
    print("%s放下了面条" % name)
    lock1.release()


def eat2(name):
    lock2.acquire()
    print("%s抢到了筷子" % name)
    time.sleep(1)
    lock1.acquire()
    print("%s抢到了面条" % name)
    time.sleep(1)
    lock1.release()
    print("%s放下了面条" % name)
    time.sleep(1)
    print("%s放下了筷子" % name)
    lock2.release()

if __name__ == '__main__':

    for name in ['egon', 'jason', 'ly']:
        t = Thread(target=eat1, args=( name,))
        t.start()

    for name in ['qq', 'tom', 'kevin']:
        t1 = Thread(target=eat2, args=(name,))
        t1.start()
递归锁案列
递归锁的特点:
可以被连续的acquire和release
但是只能被第一个抢到这把锁的执行上述操作
他的内部有一个计数器,每acquire一次计数加一次 每release一次计数减一次,只要计数不为0,那么其他人就无法抢到该锁

信号量

信号量在不同的阶段可能对应不同的技术点
在并发编程中信号量指的是锁
如果我们将互斥锁比喻程一个厕所的话,那么信号量就相当于多个厕所
sm=semaphore(5)#括号内写数字,写几就表示开设几个坑位
from threading import Thread, Semaphore
from multiprocessing import Process, Lock
# Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据

import time, random

sm = Semaphore(2)


def task(i):
    sm.acquire()
    print("线程:%s,进来了" % i)
    time.sleep(random.randint(1, 3))
    print("线程:%s,出去了" % i)
    sm.release()


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

Event事件

一些线程或者进程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样

from threading import Thread,Event
import time
event=Event()   #造了一个红绿灯
def light():
    print("红灯亮着的")
    time.sleep(3)
    print("绿灯亮了")
    event.set()
def car(name):
    print("%s 车正在等红灯" %name)
    event.wait()
    print("%s 车加油门开走了" %name)
if __name__=="__main__":
    t=Thread(target=light)
    t.start()
    for i in range(10):
        t=Thread(target=car,args=("%s" %i))
        t.start()
posted @ 2021-09-03 18:54  甜甜de微笑  阅读(36)  评论(0编辑  收藏  举报