一。GIL

  什么是GIL?

  GIL是一个全局排他锁,简单来说就是为了防止多线程并行操作的锁。这里有官方解释:

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.)
在cpython中,全局解释器锁(gil)是一个互斥体,它可以防止多个本
机线程同时执行python字节码。这个锁是必要的,主要是因为cpython
的内存管理不是线程安全的。(然而,由于gil存在,其他特性也越来越
依赖于它所执行的保证。)

  GIL的存在是因为CPython解释器的内存管理不是线程安全的

  在一个进程中有多个线程,多个线程之间如果可以并行会是什么样子?以垃圾回收机制为例。

  垃圾回收机制也是一种线程,在解释器中,每创建一个程序就会加载一次解释器。
  如果python中没有GIL的话就可能出现一种情况,当线程a在运行时创建了一个名称a,而立刻运行了垃圾回收机制,垃圾回收机制就立刻把那个名称a给回收了等到再次运行线程a时,会出现名称空间丢失的情况,
  这是不被允许的。所以GIL就是为了使得所有线程都是串行运行,但是这样会大大降低运行的效率。而且没有了多核优势,但是保证了数据的安全性。

  事实上,GIL并不是python的特点,他是所有解释性语言的通病,在python的编译器中有很多有GIL的编译器。

  那么,是不是意味着有了GIL就可以不用其他锁了呢。答案显然是否定的,

from threading import Thread
import time n = 100
def task():
global n
tmp = n
# time.sleep(1)
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)

   在线程中有io操作,使得其他线程有了抢锁的时间,导致最后的结果不是0,所有还是要根据不同数据添加不同的锁。

二。多线程的作用。

  从当前来看,多进程可以支持并行操作,在当下多核处理器为主流的情况下是不是就不需要多线程了呢。

  这要从各个进程/线程的计算密集度与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()) # 本机为6核
start=time.time()
for i in range(6):
# p=Process(target=work) #耗时 4.732933044433594
p=Thread(target=work) #耗时 22.83087730407715
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))

  io密集型

def work():
time.sleep(2) if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为6核
start=time.time()
for i in range(40):
p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
# p=Thread(target=work) #耗时2.051966667175293s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))

  从这就可以看出如果是计算密集型的就是使用进程比较有优势,因为可以多核计算,然而就io密集型的就是要线程居多,毕竟cpu利用率高还需要创建大量的进程,比较耗时间。

二。死锁:

  死锁就是在并发进程中因为争夺系统资源而产生相互等待的现象。

  四个必要条件:

  1。互斥。

  2.占有并等待

  3.不可抢占

  4.循环等待

  例子:

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

  定义一个自己的线程类,其中run方法在运行是,需要经过两个锁的过程,当10个线程先后执行时,第一个获取ab锁的人在获取b锁后再获取a锁,会发现锁被其他线程给站住了,双方互相站住了对方所需要的资源,而自己这边也有对方需要的资源,这样就形成了死锁。

  不要轻易的处理死锁的问题。

  递归锁。

  在python中有递归锁的概念。递归锁是一个可以连续使用的锁,Rlock可以被第一个抢到锁的人连续的acquire和release

  每acquire一次锁身上的计数加1

  每release一次锁身上的计数减1

  只要锁的计数不为0 其他人都不能抢

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

三。信号量

  信号量是一个为线程或进程服务的概念,设置一个指定数量的占位,就是最多可以容纳该数的进程或线程执行,当其被占满时,就会处于等待的状态,每个进程或线程在运行时都会执行锁操作,运行结束执行释放锁操作。例子:

from threading import Semaphore,Thread
import time
import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name):
sm.acquire()
print('%s占了一个坑位'%name)
time.sleep(random.randint(1,3))
sm.release() for i in range(40):
t = Thread(target=task,args=(i,))
t.start()

四。event事件

  正常情况下使用join方法可以实现主程序等待子程序的功能,但是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(10):
t = Thread(target=car,args=('伞兵%s'%i,))
t.start()

五。线程q

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

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

  锁操作的不好极容易产生死锁现象.

  queue.Queue() ,先进先出的队列

  queue.LifoQueue(),先进后出的队列(栈)

  queue.PriorityQueue(),可以设置优先级的队列,优先级数字越低,就越先被拿出

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

补充

  不使用socketserver实现TCP并发。

  思路:使用多线程,讲链接conn当成参数传给线程,多线程可以使其链接多个客户端

import socket

client = socket.socket()
client.connect(('127.0.0.1',8080)) while True:
msg = input('>>>').encode('utf-8')
client.send(msg)
recv_msg = client.recv(1024)
print(recv_msg)

客户端

import socket
from threading import Thread server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5) def talk(conn):
while True:
try:
msg = conn.recv(1024)
return_s = msg
conn.send(return_s)
except ConnectionResetError:
print('断开链接')
break
conn.close() while True:
conn,addr = server.accept()
t = Thread(target=talk,args=(conn,))
t.start()

服务器

在cpython中,全局解释器锁(gil)是一个互斥体,它可以防止多个本机线程同时执行python字节码。这个锁是必要的,主要是因为cpython的内存管理不是线程安全的。(然而,由于gil存在,其他特性也越来越依赖于它所执行的保证。)

day32_8_14 并发编程三 线程的GIL的更多相关文章

  1. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  2. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  3. 并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  4. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  5. Java并发编程:线程控制

    在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期.这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态 ...

  6. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  7. Java并发编程三个性质:原子性、可见性、有序性

      并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确  线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的 ...

  8. Java并发编程:线程池的使用(转载)

    转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  9. Java并发编程:线程池的使用(转载)

    文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

随机推荐

  1. 牛客小白月赛18 Forsaken喜欢数论

    牛客小白月赛18 Forsaken喜欢数论 题目传送门直接点标题 ​ Forsaken有一个有趣的数论函数.对于任意一个数xxx,f(x)f(x)f(x)会返回xxx的最小质因子.如果这个数没有最小质 ...

  2. 很多人都会做错的一道JVM题?【分享】

    有关Java虚拟机类加载机制相关的文章一搜一大把,笔者这儿也不必再赘述一遍了.笔者这儿捞出一道code题要各位大佬来把玩把玩,假定你一眼就看出了端倪,那么祝贺你,你可以下山了:​ public cla ...

  3. Luogu P4068 [SDOI2016]数字配对

    反正现在做题那么少就争取做一题写一题博客吧 看到题目发现数字种类不多,而且结合价值的要求可以容易地想到使用费用流 但是我们如果朴素地建图就会遇到一个问题,若\(i,j\)符合要求,那么给\(i,j\) ...

  4. 使用JaCoCo统计单元测试代码覆盖率

    1 JaCoCo介绍 JaCoCo是EclEmma团队基于多年覆盖率库使用经验总结而研发的一个开源的Java代码覆盖率库. 2 JaCoCo覆盖率计数器 JaCoCo 包含了多种尺度的覆盖率计数器(C ...

  5. 强大的性能监测工具dstat

    强大的性能监测工具dstat 本节分为以下几个部分: dstat介绍: dstat命令是一个用来替换vmstat.iostat.netstat.nfsstat和ifstat这些命令的工具,是一个全能系 ...

  6. [开源]OSharpNS 步步为营系列 - 2. 添加业务数据层

    什么是OSharp OSharpNS全称OSharp Framework with .NetStandard2.0,是一个基于.NetStandard2.0开发的一个.NetCore快速开发框架.这个 ...

  7. 在Azure DevOps Server (TFS)的流水线中编译和测试Xcode移动应用(iPhone)

    概述 Xcode是开发基于苹果macOS系统的桌面应用和移动应用的主要IDE工具.使用Azure DevOps Server (原名TFS)系统中的pipelines流水线功能,可以方便的集成Xcod ...

  8. 设置 VMware 中的 Mac OS 虚拟机进入 Recovery 模式

    Ø  简介 本文主要介绍 VMware 中的 Mac OS 虚拟机如何进入 Recovery 模式的方法,具体步骤如下: 1.   选择 Mac OS 虚拟机,点击"打开电源是进入固件&qu ...

  9. 分布式中session共享的解决方案:spring-session

    Session是客户端与服务器通讯会话跟踪技术,是服务器与客户端保持整个通讯的会话基本信息.客户端在第一次访问服务器的时候,服务端会响应一个sessionId并且将它存入到本地的Cookie中,在之后 ...

  10. 截图自动添加水印图片工具 pickpick设置中文语言

    推荐一款截图工具,主要是可以截图自动带水印,效果不错 最近发现我的不少文章被转载的到处都是.乱七八糟,这个功能后续准备做个水印用起来,感觉不错 主角介绍 首先介绍下主角 PickPick