线程GIL锁

1、多线程实现TCP的并发

TCP服务端实现并发
1.将不一样的功能尽可能拆分红不一样的函数
   拆分出来的功能能够被多个地方使用
2.将链接循环和通讯循环拆分红不一样的函数
3.将通讯循环作成多线程html

import socket
from threading import Thread
import time

"""
服务端
    1.要有固定的IP和PORT
    2.24小时不间断提供服务
    3.可以支持并发
"""

server = socket.socket()
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'))
            time.sleep(10) #开启一个客户端的时候10s打印一个结果,多个客户端就会有并发效果,开10个感受上10s打印10个
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

while True:
    conn, addr = server.accept()  # 监听 等待客户端的链接  阻塞态
    print(addr)
    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'))
客户端

 

1、GIL(global interpreter lock)介绍(******)

1.GIL基本认识

1.  只有python有GIL吗?python

GIL全局解释器锁目前是全部解释型语言的通病不可调和,必须加锁,编译型语言在编译的时候已经管理好了多线程运行的问题
有人给处理的数据加锁,虽然能够,可是那样就会有不少锁,出现大量的串行,给解释器加锁虽然也是串行,可是至少还能够实现并发

web

2.  GIL全局解释器锁的存在是python的问题么?
  GIL并非Python的特性,Python彻底能够不依赖于GIL,只是Cpython的内存管理不是线程安全,就引入这一个概念,JPython就不存在安全

 

内存管理(垃圾回收机制)
        引用计数:  值与变量的绑定关系的个数
        标记清除:  当内存快要满的时候 会自动中止程序的运行 检测全部的变量与值的绑定关系
               给没有绑定关系的值打上标记,最后一次性清除
        分代回收:  (垃圾回收机制也是须要消耗资源的,而正常一个程序的运行内部会使用到不少变量与值
                而且有一部分相似于常量,减小垃圾回收消耗的时间,应该对变量与值的绑定关系作一个分类)
                    新生代(5S)》》》青春代(10s)》》》老年代(20s)
                       垃圾回收机制扫描必定次数发现关系还在,会将该对关系移至下一代
                          随着代数的递增 扫描频率是下降的多线程

3. 做用原理:并发

      将并发运行变成串行,牺牲效率来提升数据的安全(全部互斥锁的本质)
      控制同一时间内共享数据只能被一个线程所修改(不能并行可是可以实现并发)app

4.缘由详解

一个进程中必带一个解释器和一个垃圾回收线程
    一个进程下的多个线程都须要运行,就必须去调解释器,垃圾回收线程也要用解释器,
若是不加限制,若是回收机制和其余线程同时使用解释器,会同时执行,回收机制就可能误删掉那些刚建立还没来得及绑定的变量资源
所以必须给解释器加锁,只能容许一个线程使用,我干活的时候你滚一边去,不要干扰我,这样才不会冲突

 二、多核单核与多线程

进程能够利用多核,可是开销大,python的多线程开销小,但却没法利用多核优点,难道说说python多线程没用了?dom

同一个进程下的多个线程虽不能实现并行,可是可以实现并发
多个进程下的线程可以实现并行,发挥多核优点socket

对计算来讲,cpu越多越好,可是对于I/O来讲,再多的cpu也没用ide

固然对运行一个程序来讲,随着cpu的增多执行效率确定会有所提升(无论提升幅度多大,总会有所提升)

这是由于一个程序基本上不会是纯计算或者纯I/O

因此咱们只能相对的去看一个程序究竟是计算密集型仍是I/O密集型,从而进一步分析python的多线程到底有无用武之地

#分析:
咱们有四个任务须要处理,处理方式确定是要玩出并发的效果,解决方案能够是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核状况下: 
  若是四个任务是计算密集型,没有多核来并行计算,方案一徒增了建立进程的开销,开线程牛逼   若是四个任务是I/O密集型,建立进程的开销大,且进程的切换速度远不如线程,开线程牛逼 #多核状况下:
  若是四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,开进程牛逼   若是四个任务是I/O密集型,再多的核去开再多进程仍是要一个个等IO的时间,也解决不了I/O问题,多线程节约了资源还交互进行提升效率,开线程牛逼
目前计算机都是多核,因此:

计算密集型:多线程还不如串行(没有大量切换),多核多进程牛逼
IO密集型: 多线程明显提升效率,多核没屌用
多线程和多进程都有本身的优势,要根据项目需求合理选择
目前大多数软件都是IO密集型:socket 爬虫 web
也有计算密集型的:金融分析

 

# IO密集型
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2) #睡觉也是典型的IO操做


if __name__ == '__main__':
    l=[]
    print(os.cpu_count()) #获取本机cpu数量
    start=time.time()
    for i in range(300):
        # p=Process(target=work) #用进程跑 耗时12.69s
        p=Thread(target=work) #用线程跑 耗时2.03s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
IO密集型进程线程运行比较

 

# 计算密集型
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(100000000):
        res*=i


if __name__ == '__main__':
    l=[]
    print(os.cpu_count())  # 本机为4核
    start=time.time()
    for i in range(8):
        p=Process(target=work) #耗时24.21s
        # p=Thread(target=work) #耗时43.61s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))
计算密集型进程线程运行比较

 

2、GIL与普通互斥锁 

对于不一样的数据,要想保证安全,须要加不一样的锁处理
GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
保证的是同一个进程下多个线程之间的安全

 

#GIL内置存在,只容许一个线程经过,但数据依然不安全

from threading import Thread
import time
n = 100

def task():
    global n
    tmp = n
    time.sleep(0.1)
# 在睡觉的状况下,输出99,1拿到全局锁遇到IO去睡觉时,GIL必须交出来,2抢到,1还没运行完,0.1s足够你们能抢一遍GIL,因此都拿到100去睡觉了
# 不睡觉的状况下,输出0,1抢到GIL没有IO会一直拿着直到输出后释放,释放后2才能抢到GIL才能继续搞
    n = tmp -1

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(n)

 

#加自定义锁保证数据安全

from threading import Thread,Lock
import time
n = 100 mutex = Lock() def task():
    global n
    tmp = n  #99 放在这里,tmp受到GIL控制,GIL尚未出计算结果就释放,0.1s的睡眠时间GIL够你们搞一遍了,也就是都获得了tmp=100
    mutex.acquire()
    # tmp = n  #0 放在这里,tmp受到自定义锁控制,自定义锁必须在运行结束才能释放,你们拿到的n都是上次结果-1
    time.sleep(0.1)
    n = tmp -1 mutex.release()

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(n)

 

3、死锁、递归锁现象

进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额

1.只要类加括号实例化对象
不管传入的参数是否同样生成的对象确定不同,不信你打印id
单例模式除外
mutexA = Lock() mutexB = Lock()
mutexA 和 mutexB 可不是同样东西
2.链式赋值出来的对象那但是一毛同样 mutexA = mutexB = RLock() # A B如今是同一把锁

 

1. lock 死锁现象

lock锁 一次acquire必须对应一次release,不能连续acquire

所谓死锁: 是指两个或两个以上的进程或线程在执行过程当中,因争夺资源而形成的一种互相等待的现象,(你拿了我想要的锁,我拿了你想要的锁)

若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

以下就是死锁:

from threading import Thread,Lock,current_thread,RLock
import time
"""
自定义锁一次acquire必须对应一次release,不能连续acquire 
本身千万不要轻易的处理锁的问题  
"""
mutexA = Lock()
mutexB = Lock()

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

    def func1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)

    def func2(self):
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s抢到了A锁' % self.name)
        mutexA.release()
        print('%s释放了A锁' % self.name)
        mutexB.release()
        print('%s释放了B锁' % self.name)

for i in range(3):
    t = MyThread()
    t.start()
'''
Thread-1抢到了A锁
Thread-1抢到了B锁
Thread-1释放了B锁
Thread-1释放了A锁
Thread-1抢到了B锁
Thread-2抢到了A锁
#到这里以后就出现死锁了,你想要的个人锁我想要你的锁
'''
通常的lock 死锁现象

2.Rlock递归锁  解决死锁

解决死锁方法,递归锁,在Python中为了支持在同一线程中屡次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源能够被屡次require。直到一个线程全部的acquire都被release,其余的线程才能得到资源。上面的例子若是使用RLock代替Lock,则不会发生死锁:

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

"""
mutexA = mutexB = RLock()  # A B如今是同一把锁,抢锁以后会有一个计数 抢一次计数加一 针对的是第一个抢到个人人
print(id(mutexB)) #35379080
print(id(mutexA)) #35379080

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

    def func1(self):
        mutexA.acquire()
        print('%s抢到了A锁'%self.name)  # self.name等价于current_thread().name
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        mutexB.release()
        print('%s释放了B锁'%self.name)
        mutexA.release()
        print('%s释放了A锁'%self.name)

    def func2(self):
        mutexB.acquire()
        print('%s抢到了B锁'%self.name)
        time.sleep(1)
        mutexA.acquire()
        print('%s抢到了A锁' % self.name)
        mutexA.release()
        print('%s释放了A锁' % self.name)
        mutexB.release()
        print('%s释放了B锁' % self.name)

for i in range(3):
    t = MyThread()
    t.start()
Rlock 递归锁

4、Semaphore  信号量 

信号量可能在不一样的领域中 对应不一样的知识点

互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)

from threading import Semaphore,Thread
import time
import random


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

def task(name):
    sm.acquire()
    print('%s占了一个坑位'%name)
    time.sleep(random.randint(1,30))
    print('%s放出一个坑位' % name)
    sm.release()

for i in range(5):
    t = Thread(target=task,args=(i,))
    t.start()
'''
0占了一个坑位
1占了一个坑位
2占了一个坑位

2放出一个坑位
3占了一个坑位
0放出一个坑位
4占了一个坑位
1放出一个坑位
3放出一个坑位
4放出一个坑位
'''
Semaphore 抢厕所

5、event事件  子等子

from threading import Event,Thread
import time

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

def car(name):
    print('%s正在等红灯'%name)
    e.wait()  # 等待信号
    print('%s加油门飙车了'%name)

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

for i in range(3):
    t = Thread(target=car,args=('伞兵%s'%i,))
    t.start()
'''
红灯正亮着
伞兵0正在等红灯
伞兵1正在等红灯
伞兵2正在等红灯
绿灯亮了
伞兵1加油门飙车了
伞兵2加油门飙车了
伞兵0加油门飙车了

'''
event set wait 等红绿灯

6、堆栈(先进后出)、自定义优先级队列

同一个进程下的多个线程原本就是数据共享 为何还要用队列

由于队列是管道+锁 使用队列你就不须要本身手动操做锁的问题

由于锁操做的很差极容易产生死锁现象
1.正常队列 先进先出
q = queue.Queue()
q.put('hahha')
print(q.get())

2.先进后出
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())  #3

3.自定义优先级 越小越优先
q = queue.PriorityQueue()
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get())  #(-10, 'yyyy')
相关文章
相关标签/搜索