线程2....GIL

1.GIL全局解释器锁(******) 
  GIL:全局解释器global interpreter lock,是一个互斥锁,存在于Cpython解释器内:
    保证线程的安全(以牺牲效率来换取数据的安全)
    阻止同一个进程内多个线程同时执行(不能并行但是能够实现并发)

    线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行
      同一进程下的多个线程在同一时刻只能有一个线程被执行,
      保证了同一个进程下多个线程之间的安全
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. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)
PIC定义

GIL全局解释器存在的原因:Cpython解释器的内存管理不是线程安全的

Cpython解释器的内存管理:垃圾回收机制    

1)引用计数    

2)标记清除    

3)分代回收  

垃圾回收机制也是一个任务,和普通的代码不是串行运行,如果是串行会明显有卡顿
垃圾回收到底是开进程还是开线程?肯定是线程,所以想运行也必须要拿到python解释器
假设能够并行,会出现什么情况?一个线程刚好要造一个a=1的绑定关系之前,这个垃圾线程来扫描,矛盾点就来了,谁成功都不对!
也就意味着在Cpython解释器上有一把GIL全局解释器锁   

所以在Cpython解释器中,同一个进程下:

  多个线程不能实现并行但是能够实现并发

  多个进程下的线程能够实现并行 

 

问题:python多线程是不是就没有用了呢?

 1)四个任务:计算密集的任务,每个任务耗时10s

单核情况下:    

  多线程好一点,消耗的资源少一点

多核情况下:    

  开四个进程:10s多一点    

  开四个线程:40s多一点

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())  # 本机为8核
    start = time.time()
    for i in range(8):
        p=Process(target=work) # 耗时9.252728939056396
        p = Thread(target=work)  # 耗时35.369622230529785
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))
计算密集型

2)四个任务:IO密集的任务,每个任务io 10s

单核情况下:    

  多线程好一点

多核情况下:    

  多线程好一点

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


def work():
    time.sleep(2)


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为8核
    start = time.time()
    for i in range(600):
        p = Process(target=work)  # 耗时4.699530839920044
        # p=Thread(target=work) # 耗时2.054128885269165
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))
IO密集型

  >>> 多线程和多进程都有自己的优点,要根据项目需求合理选择 


2.GIL全局解释器锁与普通的互斥锁的区别
  1)GIL并不能保证数据的安全,它是对Cpython解释器加锁,针对的是线程
  2)对于不同的数据,要想保证安全,需要加不同的锁
from threading import Thread, Lock
import time

mutex = Lock()

n = 100


def task():
    global n
    mutex.acquire()
    tmp = n
    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.死锁与递归锁(了解)
  自定义锁:一次acquire必须对应一次release,不能连续acquire
  递归锁:可以连续的acquire,每acquire一次计数加一(针对的是抢到锁的人)
from threading import Thread,Lock,RLock
import time

# mutexA = Lock()
# mutexB = Lock() # 形成死锁
mutexA = mutexB = RLock()  # 抢锁一次计数加一,针对的是第一个抢到锁的人


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

    def func1(self):
        mutexA.acquire()
        print('%s 抢到A锁了' % self.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(100):
    t = MyThead()
    t.start()

4.信号量(了解)
  互斥锁:比喻为独立卫生间,所有人抢一把锁
  信号量:比喻为有多个隔间的卫生间,所有人抢多把锁
from threading import Thread, Semaphore
import time
import random

sm = Semaphore(5)  # 五个厕所五把锁


def task(name):
    sm.acquire()
    print('%s正在蹲坑' % name)
    # 模拟蹲坑耗时
    time.sleep(random.randint(1, 5))
    sm.release()


if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=task, args=('伞兵%s号' % i,))
        t.start()

5.event事件
  一些线程需要等待另外一些线程运行完毕才能运行,类似于发射信号一样
from threading import Event, Thread
import time

event = Event()


def light():
    print('红灯亮着!')
    time.sleep(3)
    event.set()  # 解除阻塞,给另一个子线程发送信号
    print('绿灯亮了!')


def car(i):
    print('%s 正在等红灯了' % i)
    event.wait()  # 阻塞,等待接收set()信号
    print('%s 加油门飙车了' % i)


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

for i in range(10):
    t = Thread(target=car, args=(i,))
    t.start()

6.线程queue
同一个进程下的线程数据都是共享的为什么还要用queue?
  queue本身自带锁的功能,能够保证数据的安全

  1)Queue 队列:先进先出
  2)LifoQueue 堆栈:先进后出 last in first out
  3)PriorityQueue 优先:所设数字越小,优先级越高
import queue


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

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



q = queue.PriorityQueue()
q.put((10,'a'))
q.put((-1,'b'))
q.put((100,'c'))
print(q.get())
print(q.get())
print(q.get())


>>>:
  1
  2
  3

  4

  (-1, 'b')
  (10, 'a')
  (100, 'c')

7.进程池线程池(******) 
  1)socket服务端实现并发
    现在回来想网络编程服务端需要满足哪几点需求
      * 固定的ip和port
      * 24小时提供服务
      * 能够实现并发
  2)进程池线程池介绍
    无论是开线程还是开进程其实都消耗资源,开线程消耗的资源比开进程的小
    线程不可能无限制的开下去,总要消耗和占用资源

    池:  
      为了减缓计算机硬件的压力,避免计算机硬件设备崩溃  
      虽然减轻了计算机硬件的压力,但是一定程度上降低了持续的效率
    进程池线程池:  
      为了限制开设的进程数和线程数,从而保证计算机硬件的安全

8.如何使用进程池线程池(******)
    * concurrent.futures模块导入
    * 线程池创建(默认:线程数=cpu核数*5左右)
    * submit提交任务(提交任务的两种方式)
    * 异步提交的submit返回值对象
    * shutdown关闭池并等待所有任务运行结束
    * 对象获取任务返回值
    * 进程池的使用,验证进程池在创建的时候里面固定有指定的进程数
    * 异步提交回调函数的使用
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os

# 示例化池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ProcessPoolExecutor(5)  # 创建了一个池子,池子里面有5个线程


def task(n):
    print(n, os.getpid())
    time.sleep(2)
    return n ** 2


def call_back(n):
    print('拿到了结果:%s' % n.result())


"""
提交任务的方式
    同步:提交任务之后,原地等待任务的返回结果,再继续执行下一步代码
    异步:提交任务之后,不等待任务的返回结果(通过回调函数拿到返回结果并处理),直接执行下一步操作
"""

# 回调函数:异步提交之后一旦任务有返回结果,自动交给另外一个去执行
if __name__ == '__main__':
    # pool.submit(task,1)
    t_list = []
    for i in range(20):
        future = pool.submit(task, i).add_done_callback(call_back)  # 异步提交任务
        t_list.append(future)

    # pool.shutdown()  # 关闭池子并且等待池子中所有的任务运行完毕
    # for p in t_list:
    #     print('>>>:',p.result())
    print('')
线程池进程池的使用
socket并发
# -------------------服务端----------------------
import socket
from threading import Thread

"""
服务端:
    1.固定的ip和port
    2.24小时不间断提供服务
    3.支持高并发
"""
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)  # 半连接池


def communicate(conn):
    while True:
        try:
            data = conn.recv(1024)  # 阻塞
            if len(data) == 0: break
            print(data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    conn.close()


while True:
    conn, addr = server.accept()  # 阻塞
    print(addr)
    t = Thread(target=communicate, args=(conn,))
    t.start()



# -------------------客户端----------------------
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    info = input('>>>:').encode('utf-8')
    if len(info) == 0: continue
    client.send(info)
    data = client.recv(1024)
    print(data)
 
posted @ 2019-05-08 20:43  zhoyong  阅读(129)  评论(0编辑  收藏  举报