并发编程二

一.GIL全局解释器锁

二.GIL与普通的互斥锁

三.死锁与递归锁

四.信号量

五.event 事件

六.线程

  一,何为全局解释器锁

  1. python解释器锁有很多种,Jpython Ppython  最常见的就是Cpython 

    GIL其实本质也是一把互斥锁:用来阻止几个线程同时操作同一份数据,对于被操作的数据加锁 

    由并发变成了串行,虽然牺牲了效率但是保证了数据的安全 

  2.  GIL存在是因为CPpytho解释器的内存是管理线程不安全的问题,而不是线程下的数据安全的问题 所以不同的数据需要加不同的锁 

  3. 垃圾回收机制

    1.引用计数

    2.标记清除

    3.分代回收

  

工作原理 

  同一个进程下的线程是可以相互通信的 所以垃圾回收机制也有一个线程垃圾回收机制线程 

  如果在其他线程在创建a = 10  会开辟一个内存空间将10的值绑定给 变量a 但是 在你开辟内存空间10存放  然而线程的

  执行速度是非常快的 垃圾回收机制线程会将10 还未绑定a 的值 >>> 此时10 的引用计数为零 会将10这个值直接删除

  ::::这样不合理吧我刚刚创建的变量 直接给我回收了 所以才会需要GIL 全局解释器锁对我们的线程运行加以保护 

  

  4. 那又会产生问题了python多线程就没办法利用多核(进程CPU)的优势了, 多线程是不是就没有用了呢?

    很显然这样的解释是不全面的

  二. GIL锁与普通的互斥锁

  gil 是Cpython 自带的锁

from threading import Thread,Lock

# 同一进程下的线程是可以直接进行相互通信的
import time

num = 100
def task():
    global num
    temp = num  # 将num 100 赋值给temp

    time.sleep(1)  # Cpython 解释器在遇到IO 切换时 其他线程 会过来抢资源 所以打印 每次运行结果都会是99
    # 若把IO时间去除 打印结果是0 将结果执行完毕 同一个线程 第一99 循环第二次98 第三次97 一直拿着gil全局锁 直到执行完毕0
    num = temp-1


# 生成锁
mutex = Lock()
t_list = []
for i in range(100):

    t = Thread(target=task,args=())
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()  # 所有的子线程 执行完毕 才执行主线程 主线程执行完毕 意味回收所有的子线程的资源 清除
print('主线程')  #
print(num)

  加普通锁

from threading import Thread,Lock

# 同一进程下的线程是可以直接进行相互通信的
import time

num = 10
def task(mutex):
    global num
    # 开始抢锁
    mutex.acquire()

    temp = num  # 将num 100 赋值给temp

    time.sleep(1)  # Cpython 解释器在遇到IO 切换时 其他线程 会过来抢资源 所以打印 每次运行结果都会是99
    # 若把IO时间去除 打印结果是0 将结果执行完毕 同一个线程 第一99 循环第二次98 第三次97 一直拿着gil全局锁 直到执行完毕
    num = temp-1
    # 执行完毕 释放锁
    mutex.release()  # 其他线程可以过来抢锁啦

# 生成锁
mutex = Lock()
t_list = []
for i in range(10):

    t = Thread(target=task,args=(mutex,))
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()  # 所有的子线程 执行完毕 才执行主线程 主线程执行完毕 意味回收所有的子线程的资源 清除
print('主线程')  #
print(num)
研究python的多线程是否有用是需要分情况讨论的?

开了四个任务(函数)  计算密集型的 IO切换少 每个  任务10S
单核的情况下
    开线程更节省资源  单核是不能并行 可实现并发 一个进程多个线程
多核情况下
    可以实现并行 也就是说4个任务所耗费的时间 10S 多一点
    线程是需要自行完其中一个任务在 切换 另一个  执行任务 也就是40S多

四个任务 IO密集的
单核下
    
    开线程更节省资源
多核的情况下
    也是开线程更节省资源  IO 切换40S 多一点  如果是开进程的话 那就是要
重新开辟四个进程 内存空间 存放代码 在主线程下 执行代码任务 大概耗时是
多核中只要开一个进程 在 进程下开四个线程就可以了 

  补充:如何不用serversocket 模块 服务端可以接收多个客户端的访问 也是是服务端的并发量 可以实现统一时间内的多个客户端进行资源请求和访问 TCP 建立连接 完成数据交互

  服务端

# 利用线程实现服务端的并发


import socket

server = socket.socket()
server.bind(('127.0.0.1', 8088))
server.listen(5)


def talk(conn):
    # 一个线程就是一个新的资源请求
    while True:
        try:  # 数据交互的地方进行异常捕获

            data = conn.recv(1024)
            if data == 0:
                break
            # 发送
            conn.send(data.upper())
        except ConnectionRefusedError as e:
            print(e)
            break
# 链接循环
while True:
    conn,addr = server.accept()

    # 通讯怎那么办 现在只能在同一时间内有一个人链接
    # 原本可
    # while True:
    #     try:  # 数据交互的地方进行异常捕获
    # 
    #         data = conn.recv(1024)
    #         if data == 0:
    #             break
    #         # 发送
    #         conn.send(data.upper())
    #         print(addr) 
    #         #  # ('127.0.0.1', 14062)
    #         #     # ('127.0.0.1', 14062)
    #         #     # ('127.0.0.1', 14062)
    #         #     # ('127.0.0.1', 14062)
    #         #     # ('127.0.0.1', 14062)
    #         #     # ('127.0.0.1', 14062)  # 一次只能联一个 一方终止则直接终断 
    #     except ConnectionRefusedError as e:
    #         print(e)
    #         break
    #    一个线程就是一个新的资源请求

    # 设置线程
    t = Thread(target=talk, args=(conn,))
    print(conn)
    print(addr)  #  ('127.0.0.1', 14004)   ('127.0.0.1', 14005) ('127.0.0.1', 14007)  ('127.0.0.1', 14008)
    # 每个线程的就是一个客户端 重新开一个线程 IO 来回切换 看起来像是同时运行 并发数 客户请求数 ip+PORT
    t.start()  # 开启执行线程任务 调用run()方法执行 线程下的talk()代码

  进程实现并发

# 进程
import json
import time
from socket import *
from multiprocessing import Process
from threading import Thread
server = socket(AF_INET, SOCK_STREAM)
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)


# server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)



def f1(conn):
    # 接收消息
    while True:
        try:

            data = conn.recv(1024)
            print(data.decode('utf-8'))
            conn.send(b'hello my girl')
        except BaseException as e:
            print(e)
            break


        # 进程并发
if __name__ == '__main__':
    while True:
        print(123)
        conn, addr = server.accept()

        print(addr)
        #
        time.sleep(2)

        d = Process(target=f1,args=(conn,))
        d.start()
    # t = Thread(target=f1,args=(conn,))
    # t.start()

  客户端

# 客户端
import socket

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

while True:
    # 通信循环
    client.send(b'hello big baby')
    # 接收信息
    data = client.recv(1024)

    print(data.decode('utf-8'))

  三死锁 和递归锁 >>>利用引用计数的原理 直到 释放锁的引用计数为零 其他线程可以进城抢锁 

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

"""
# mutexA = Lock()
# mutexB = Lock()
mutexA = mutexB = RLock()  # A B现在是同一把锁


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(10):
    t = MyThread()
    t.start()




# class Demo(object):
# #     pass
# #
# # obj1 = Demo()
# # obj2 = Demo()
# # print(id(obj1),id(obj2))
"""
只要类加括号实例化对象
无论传入的参数是否一样生成的对象肯定不一样
单例模式除外


自己千万不要轻易的处理锁的问题  

"""

  四信号量

  和锁有点像 锁是谁抢到锁 谁就用 好比一个宿舍的一个卫生间 谁先抢到谁先用

  信号量好比一个公共厕所 有5个卫生间 比如来了11个人可以先让5个人先上 出来一个 在进去一个

  

import random
import time
from threading import Semaphore,Thread

"""
信号量可能在不同的领域中 对应不同的知识点
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)  其实也是一个大锁

"""
# 生成一个信号量对象
sm = Semaphore(5)  # 设置了5个坑位
def task(i):
    sm.acquire()
    print('%s 占了一个坑位'%i)

    time.sleep(random.randint(1,3))
    # 释放大锁
    sm.release()

for i in range(20):
    t = Thread(target=task,args=(i,))
    t.start()

  

  五event 事件

  

# event 事件

# 等待红绿灯事件
import time
from threading import Thread,Event

# 生成一个event 对象
e = Event()
def light():
    print('红灯亮着')
    # 虚拟世间
    time.sleep(3)
    e.set()  # 发送信号
    # 世间到了
    print('绿灯亮了')
def cart(name):
    print('%s 正在等红灯'% name)
    e.wait()  # 等待上面发送信号
    print('%s加油门发车了'% name)

# 线程
t1 = Thread(target=light)
t1.start()

# 等待的车子
for i in range(5):
    t2 = Thread(target=cart,args=('车手%s'%i,))
    t2.start()

 六线程q 队列后进后出(堆栈) 优先级q.put(item,'数据')

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

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

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



# q = queue.Queue()
# q.put('hahha')
# print(q.get())


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


# q = queue.PriorityQueue()
# # 数字越小 优先级越高
# q.put((10,'haha'))
# q.put((100,'hehehe'))
# q.put((0,'xxxx'))
# q.put((-10,'yyyy'))
# print(q.get())
原文地址:https://www.cnblogs.com/mofujin/p/11353580.html