python语法基础-并发编程-线程-各种锁以及队列

###############    守护线程    ##############

from threading import Thread
import time
def func1(name):
    while True:
        print(11111111)
        time.sleep(1)

def func2(name):
    print(2222222)
    time.sleep(5)


if __name__ == '__main__':
    t=Thread(target=func1,args=('andy',))
    t.daemon = True  # 主线程代码结束,子线程随之结束,
    # 不加守护线程,主线程就会等待子线程的结束,然后主线程才会结束,
    t.start()

    t2=Thread(target=func2,args=('lucy',))
    t2.start()
    # 主线程会等待子线程结束
    print('主线程')
    print(t.is_alive())
    '''
    11111111
    2222222
    主线程
    True
    11111111
    11111111
    11111111
    11111111
    11111111
    '''

分析:

# 所以进程的守护进程,和线程的守护进程是有不一样的点的
# 1.对主进程来说,运行完毕指的是主进程代码运行完毕
# 2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),
# 然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。
# 因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

 ###############    同步锁    ##############

# 线程加同步锁,就是说其他的线程要等待那个拿到锁的线程结束了之后才可以去操作数据,也可以叫做互斥锁,

#
模拟多线程出错的场景, # 全局的n是10,然后创建10个线程,去执行n-1的操作, # 结果按理说应该是0,但是结果是9, # 为什么? # 10个线程去拿n=10,所有人拿的都是10,然后都同步减去1,然后返回都是9,所以是9, # 但是有GIL锁啊,为什么还会产生这种情况? # 还是因为时间片轮转,你取到了数据,但是还没有来得及减1,这个值就被其他的线程拿走了,还是10, from threading import Lock, Thread import time # def func(): # global n # temp = n # time.sleep(0.2) # n= temp-1 # # n = 10 # t_list=[] # for i in range(10): # t= Thread(target=func) # t.start() # t_list.append(t) # # # for i in t_list:t.join() # print(n) # 模拟线程加锁的情况,然后看看结果 # 这样加了锁之后,结果应该就是0了,是的,结果是0了 # 加锁降低了性能,但是保证了数据的安全性, # def func(lock): # global n # lock.acquire() # temp = n # time.sleep(0.2) # n= temp-1 # lock.release() # # n = 10 # t_list=[] # lock = Lock() # # for i in range(10): # t= Thread(target=func,args=(lock,)) # t.start() # t_list.append(t) # # # for i in t_list:t.join() # print(n)

互斥锁和join的区别

#有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
#没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是 #start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的 #单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.

###############   死锁和递归锁   ##############

# 现在看看科学家吃面的问题,看看死锁的场景
# 这个例子是只有拿到面和叉子,才可以吃面,
from threading import Lock, Thread
import time
# noodle_lock = Lock()
# fork_lock = Lock()
# def eat1(name):
#     noodle_lock.acquire()
#     print("%s拿到面条"%name)
#     fork_lock.acquire()
#     print("%s拿到叉子"%name)
#     print("吃面")
#     fork_lock.release()
#     noodle_lock.release()
#
#
#
# def eat2(name):
#     fork_lock.acquire()
#     print("%s拿到叉子"%name)
#     time.sleep(1)
#     noodle_lock.acquire()
#     print("%s拿到面条"%name)
#     print("吃面")
#     noodle_lock.release()
#     fork_lock.release()
#
# Thread(target=eat1,args=("name1",)).start()
# Thread(target=eat2,args=("name2",)).start()
# Thread(target=eat1,args=("name3",)).start()
# Thread(target=eat1,args=("name4",)).start()
#
# """
# name1拿到面条
# name1拿到叉子
# 吃面
# name2拿到叉子
# name3拿到面条
# 这种情况不同的人拿到了叉子和面条,就无法进行了,就阻塞了,就死锁了,
# 怎么解决?有一个递归锁
# """


# 递归锁,来解决死锁的问题
# # 你可以看做是钥匙串上面的两把钥匙,一旦你拿到了一把钥匙,就证明你拿到了整个钥匙串了,
from threading import RLock

noodle_lock = fork_lock = RLock()  # 你可以看做是钥匙串上面的两把钥匙


def eat1(name):
    noodle_lock.acquire()
    print("%s拿到面条" % name)
    fork_lock.acquire()
    print("%s拿到叉子" % name)
    print("吃面")
    fork_lock.release()
    noodle_lock.release()


def eat2(name):
    fork_lock.acquire()
    print("%s拿到叉子" % name)
    time.sleep(1)
    noodle_lock.acquire()
    print("%s拿到面条" % name)
    print("吃面")
    noodle_lock.release()
    fork_lock.release()


Thread(target=eat1, args=("name1",)).start()
Thread(target=eat2, args=("name2",)).start()
Thread(target=eat1, args=("name3",)).start()
Thread(target=eat2, args=("name4",)).start()

###############   全局解释器锁GIL   ##############

"""
全局解释器锁GIL

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。
像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。
所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。
所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

简单来说,在Cpython解释器中,因为有GIL锁的存在同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。

同一个数据,多个线程去操作,也会出现问题,所以也有线程锁的概念,
这种机制叫做全局解释器锁,英文GIL,
这种机制的结果就是:同一时间只有一个线程去访问cpu,
你想要访问数据,就必须拿到这个钥匙,
这个锁,锁的是线程,不是锁的某一个数据,
这样不好,因为同一时间cpu上面只有一个线程,这样不能充分的利用cpu
这不是Python语言的问题,这是cPython解释器的问题,如果你有一个jPython解释器就行

这的确是一个弊病,导致Python的多线程形同虚设,
那么为什么不解决呢?
java和c++都是编译性语言,Python是一个解释性语言,php也是,
目前解释性语言就是存在这个问题,这个矛盾还不可调和,

是否把数据加锁就可以了,也是不行的,数据太多了,这么大范围的加锁,最终导致的效率,还不如全局解释器锁的性能好,

常见问题1
我们有了GIL锁为什么还要自己在代码中加锁呢?
还是因为时间片轮转,你取到了数据,但是还没有来得及减1,这个值就被其他的线程拿走了,还是10,
常见问题2 有了GIL的存在,同一时刻同一进程中只有一个线程被执行,进程可以利用多核,但是开销大, 而Python的多线程开销小,但却无法利用多核优势,也就是说Python这语言难堪大用。 解答: 其实编程所解决的现实问题大致分为IO密集型和计算密集型。 对于IO密集型的场景,Python的多线程编程完全OK,而对于计算密集型的场景, Python中有很多成熟的模块或框架如Pandas等能够提高计算效率。 在cPython解释器下的Python程序,在同一时间,多个线程只能有一个线程在cpu执行,这不意味着多线程就没有意义了, 解答: 因为只有涉及到计算的地方才会使用到CPU, 高CPU:所以在计算类的高cpu利用率的,Python不占优势, 高IO:我们写的代码很多都涉及到这种, 比如qq聊天,处理日志文件,爬取网页,处理web请求,读写数据库,都是高io的,都是有Python用武之地的, 所以Python不能处理计算类高的问题,这不影响他在web编程的作用, 如果你真的需要高并发呢,你可以使用多进程,就不会有GIL锁了,
"""

 谈下python的GIL

GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),
使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,
使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。

多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大

###############    线程,队列  ##############

# 线程,队列

# 加锁会自己写代码,不方便,我们可以使用队列
# 队列内置了很多的锁,可以保证数据安全,
# queue队列 :使用import queue,用法与进程Queue一样

import queue

q = queue.Queue()

q.put()
q.put_nowait()  # 这个会报错
q.get()
q.get_nowait()  # 这个会报错,

# 队列的特点:先进先出,


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

queue.PriorityQueue()  # 优先级队列,
# put进入一个元组
# 元组的第一个元素是优先级,(通常也可以是数字,或者也可以是非数字之间的比较)  数字越小,优先级越高
q.put(20, 1)
q.put(10, 2)  # 数字越小,优先级越高,优先级一样,就是按照和ASk码排,
q.put(30, 3)
print(q.get())
print(q.get())
print(q.get())

# 总结:
# 1,普通队列
# 2,栈
# 3,优先级队列,
# 这三种都不会出现多线程抢占资源,

###########################################

############################################

原文地址:https://www.cnblogs.com/andy0816/p/12289718.html