网络并发编程(三)


一.GIL全局解释器锁

1.1 简介

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.)
"""
1.python解释器其实有很多版本(默认肯定使用的是CPython)
    Cpython、Jpython、pypython

在Cpython中GIL全局解释器锁其实也是一把互斥锁,主要用于阻止同一个进程下的多个线程同时被运行(python的多线程无法使用多核优势)
GIL肯定存在于CPython解释器中 主要原因就在于Cpython解释器的内存管理不是线程安全的

2.内存管理>>>垃圾回收机制
    引用计数
    标记清除
    分代回收
"""
1.GIL是Cpython解释器的特点
2.python同一个进程内的多个线程无法利用多核优势(不能并行但是可以并发)
3.同一个进程内的多个线程要想运行必须先抢GIL锁
4.所有的解释型语言几乎都无法实现同一个进程下的多个线程同时被运行

1.2 验证GIL的存在

from threading import Thread
import time
m = 100
def test():
    global m
    tmp = m
    tmp -= 1
    m = tmp
for i in range(100):
    t = Thread(target=test)
    t.start()
time.sleep(3)
print(m)
"""
同一个进程下的多个线程虽然有GIL的存在不会出现并行的效果
但是如果线程内有IO操作还是会造成数据的错乱 这个时候需要我们额外的添加互斥锁
"""

1.3 死锁现象

from threading import Thread, Lock
import time

A = Lock()
B = Lock()


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

    def func1(self):
        A.acquire()
        print('%s 抢到了A锁' % self.name)  # current_thread().name  获取线程名称
        B.acquire()
        print('%s 抢到了B锁' % self.name)
        time.sleep(1)
        B.release()
        print('%s 释放了B锁' % self.name)
        A.release()
        print('%s 释放了A锁' % self.name)

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

for i in range(10):
    obj = MyThread()
    obj.start()
 
"""就算知道锁的特性及使用方式 也不要轻易的使用 因为容易产生死锁现象"""

二.Python多线程

# 是否有用需要看情况而定(程序的类型)
# IO密集型
    eg:四个任务  每个任务耗时10s
        开设多进程没有太大的优势    10s+
            遇到IO就需要切换 并且开设进程还需要申请内存空间和拷贝代码
        开设多线程有优势
            不需要消耗额外的资源   10s+
# 计算密集型
    eg:四个任务     每个任务耗时10s
        开设多进程可以利用多核优势    10s+
          开设多线程无法利用多核优势   40s+
"""
多进程结合多线程
"""
"""IO密集型"""
# from multiprocessing import Process
# from threading import Thread
# import threading
# import os,time
# def work():
#     time.sleep(2)
#
#
# if __name__ == '__main__':
#     l=[]
#     print(os.cpu_count()) #本机为6核
#     start=time.time()
#     for i in range(400):
#         # p=Process(target=work) #耗时42.54s多,大部分时间耗费在创建进程上
#         p=Thread(target=work) #耗时2.08s多
#         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
def work():
    res=0
    for i in range(100000000):
        res*=i
if __name__ == '__main__':
    l=[]
    print(os.cpu_count())  # 本机为6核
    start=time.time()
    for i in range(6):
        # p=Process(target=work) #耗时5.35s多
        p=Thread(target=work) #耗时23.37s多
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print('run time is %s' %(stop-start))

三.进程池和线程池

思考:能否无限制的开设进程或者线程???
    肯定是不能无限制开设的
        如果单从技术层面上来说无限开设肯定是可以的并且是最高效的
        但是从硬件层面上来说是无法实现的(硬件的发展永远赶不上软件的发展速度)
池
    在保证计算机硬件不奔溃的前提下开设多进程和多线程
        降低了程序的运行效率但是保证了计算机硬件的安全
进程池与线程池
    进程池:提前开设了固定个数的进程 之后反复调用这些进程完成工作(后续不再开设新的)
    线程池:提前开设了固定个数的线程 之后反复调用这些线程完成工作(后续不再开设新的)