day32 GIL全局解释器锁 死锁 信号量 Event事件 线程

练习:

想与多个用户进行通讯,且支持并发

实现从单线程,无法并发变成多线程,支持并发

import socket
from threading import Thread


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            if not len(data):
                break
            conn.send(data.upper())
        except ConnectionResetError as a:
            print(a)
            break
    conn.close()


while True:
    conn,addr = server.accept()
    # talk(conn)
    t = Thread(target=talk,args=(conn,))
    t.start()
练习

GIL全局解释器锁

"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly
because CPython’s memory management is not thread-safe.
"""
"""
ps:python解释器有很多种  最常见的就是Cpython解释器
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全 
用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)
    python的多线程没法利用多核优势  是不是就是没有用了?
    
GIL的存在是因为CPython解释器的内存管理不是线程安全的

内存管理: ----- 垃圾回收机制

1.引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

2.标记清除

圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间.

3.分代回收

1.刚产生的变量值放在新生代中高频率检查,如果引用计数为0,就是采用引用计数机制回收,长期存活的变量值经过多次检查后会提高分代
2.分带越高,检查频率越低,且还能继续提高一直存活的变量值的分带,从而来提高整体垃圾回收的效率

GIL的加锁与解锁时机

加锁的时机:在调用解释器时立即加锁

解锁时机:

  • 当前线程遇到了IO时释放

  • 当前线程执行时间超过设定值时释放

GIL的优点:

  • 保证了CPython中的内存管理是线程安全的

GIL的缺点:

  • 互斥锁的特性使得多线程无法并行

### 问题

GIL是python的特点吗?

不是,它是CPython解释器的特点,仅在CPython中存在.

单进程下多个线程无法利用多核优势,是所有解释性语言的通病.

针对不同的数据应该加不同的锁进行处理

  

研究python的多线程是否有用需要分情况讨论

四个任务 计算密集型的  10s
单核情况下
    开线程更省资源
多核情况下
    开进程 10s
    开线程 40s
    
四个任务 IO密集型的  
单核情况下
    开线程更节省资源
多核情况下
    开线程更节省资源 

  

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


# 计算密集型
def work():
    res = 0
    for i in range(1000):
        res*=i

if __name__ == '__main__':
     l = []
     print(os.cpu_count())
     start = time.time()
     for i in range(6):
        # p = Process(target=work)
        #创建进程
        p = Thread(target=work)
        # 创建线程
        l.append(p)
        p.start()
     for p in l:
         p.join()
     stop = time.time()
     print('run time is %s'%(stop-start))
计算密集型
from multiprocessing import Process
from threading import Thread
import os , time

# io密集型
def work():
    time.sleep(1)

if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(400):
        p = Process(target=work)
        l.append(p)
        p.start()
    for p in l:
        # print(p)
        p.join()
    stop = time.time()
    print('run time is %s'%(stop-start))
io密集型

死锁

死锁问题 当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁 这时候程序就会进程无限卡死状态 ,这就称之为死锁

from threading import Thread,Lock


mutex1 = Lock()
mutex2 = Lock()


class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()

    def func1(self):
        mutex1.acquire()
        print('%s 抢到了锁1'%self.name)
        mutex2.acquire()
        print('%s 抢到了锁2' % self.name)
        mutex1.release()
        print('%s 释放了锁1'%self.name)
        mutex2.release()
        print('%s 释放了锁2' % self.name)

    def func2(self):
        mutex2.acquire()
        print('%s 抢到了锁2'%self.name)
        mutex1.acquire()
        print('%s 抢到了锁1' % self.name)
        mutex2.release()
        print('%s 释放了锁2'%self.name)
        mutex1.release()
        print('%s 释放了锁1' % self.name)

for i in range(10):
    t = MyThread()
    t.start()
死锁

可重入锁

Rlock 称之为递归锁或者可重入锁

Rlock不是用来解决死锁问题的

与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次 如果一个线程已经执行过acquire 其他线程将无法执行acquire

from threading import Thread,RLock
"""
Rlock可以被第一个抢到锁的人连续的acquire和release
每acquire一次锁身上的计数加1
每release一次锁身上的计数减1
只要锁的计数不为0 其他人都不能抢

"""

mutex1 = mutex2 = RLock()

class MyThread(Thread):
    def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发
        self.func1()
        self.func2()

    def func1(self):
        mutex1.acquire()
        print('%s 抢到了锁1'%self.name)# self.name等价于current_thread().name
        mutex2.acquire()
        print('%s 抢到了锁2' % self.name)
        mutex1.release()
        print('%s 释放了锁1'%self.name)
        mutex2.release()
        print('%s 释放了锁2' % self.name)

    def func2(self):
        mutex2.acquire()
        print('%s 抢到了锁2'%self.name)
        mutex1.acquire()
        print('%s 抢到了锁1' % self.name)
        mutex2.release()
        print('%s 释放了锁2'%self.name)
        mutex1.release()
        print('%s 释放了锁1' % self.name)

for i in range(10):
    t = MyThread()
    t.start()
'''
只要类加括号实例化对象
无论传入的参数是否一样生成的对象肯定不一样
单例模式除外
'''
可重入锁

信号量

信号量可能在不同的领域中 对应不同的知识点
可以现在被锁定的代码 同时可以被多少线程并发访问
Lock 锁住一个马桶  同时只能有一个
Semaphore 锁住一个公共厕所    同时可以来一堆人


用途: 仅用于控制并发访问   并不能防止并发修改造成的问题
from threading import Thread,Semaphore
import time
import random

a = Semaphore(5)
# 造了一个含有五个的坑位的公共厕所

def task(name):
    a.acquire()
    print('%s 在上厕所'%name)
    time.sleep(random.randint(1,3))
    a.release()


for i in range(10):
    t = Thread(target=task,args=(i,))
    t.start()
信号量

Event事件

Event事件
事件表示在某个时间发生了某个事情的通知信号,用于线程间协同工作。

因为不同线程之间是独立运行的状态不可预测,
所以一个线程与另一个线程间的数据是不同步的
当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,
就必须保持线程间数据的同步,Event就可以实现线程间同步
from threading import Thread,Event
import time

# 生成一个event对象
e = Event()
def light():
    print('红灯亮')
    time.sleep(1)
    e.set()  # 发送信号
    print('绿灯亮')


def car(name):
    print('%s 正在等红灯'% name)
    e.wait()  #等待信号
    print('%s 加油门,踩离合'% name)


t = Thread(target=light)
t.start()

for i in range(10):
    t = Thread(target=car,args=('萝卜%s'%i,))
    t.start()
event事件
event.isSet():返回event的状态值;
event.wait():将阻塞线程;知道event的状态为True
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。

  

线程

同一个进程下的多个线程本来就是数据共享 为什么还要用队列???

因为队列是管道+锁  使用队列你就不需要自己手动操作锁的问题 

因为锁操作的不好极容易产生死锁现象

 

1.Queue 先进先出队列

与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。

import queue
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
1

2.LifoQueue 后进先出队列

该队列可以模拟堆栈,实现先进后出,后进先出

import queue
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
2

3.PriorityQueue 优先级队列

该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按照从小到大的顺序取出

import queue
q = queue.PriorityQueue()

q.put((10,'hehe'))
q.put((20,'enen'))
q.put((-10,'wenwen'))
# 取值,得到的是个元组,可以根据索引取值
print(q.get()[1])
print(q.get())
print(q.get())
print(q.get())
3

猜你喜欢

转载自www.cnblogs.com/komorebi/p/11354397.html