百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

线程同步与并发(线程同步和并发的区别)

citgpt 2024-08-01 13:30 8 浏览 0 评论

今天来学习线程同步与并发,我们先来看一下线程之间的几种通信方式:

1.线程之间的几种通信方式

  • Event:事件;
  • Critical Section:临界区;
  • Semaphone:信号量;

2.Event事件的使用

  • Event是事件处理的机制,全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True, 那么event.wait 方法时便不再阻塞;

我们先来看看下面的实例:

线程同步与并发(线程同步和并发的区别)

from threading import Thread, Event
import time

def teacher(event: Event):
 print('I am teacher , waiting for your homework')
 event.wait()
 print("I am teacher, already obtaining student's homework ")
 
def student(event: Event):
 finished_homework = []
 while len(finished_homework) < 10:
 time.sleep(1)
 print('I am student, finished one homework')
 finished_homework.append(1)
 else:
 print('student finish homework')
 event.set()
 
if __name__ == '__main__':
 event = Event()
 Thread(target=student, args=(event,)).start()
 Thread(target=teacher, args=(event,)).start()

这个实例首先从threading 模块中导入了Thread和Event两个类,然后定义了两个方法teacher和student,在if __name__ == '__main__'中执行了这两个方法。

按照正常一般的代码执行顺序,会先打印teacher方法中的第一个print,然后来到了event.wait(),wait()方法的语法为wait(self, timeout=None),timeout用于设置等待的时长,如果超过时长则不再等待,直接向下执行,如果timeout没有指定则一 直等待,等待的时候是阻塞的

我们可以看到teacher方法中的event.wait()并没有设置timeout,所以会阻塞,开始执行student方法,student方法里面是一个while循环,需要打印10遍print中的内容,打印完毕后进入else,其中有一个event.set()方法,会将flag设置为True,wait等待的线程就可以向下执行;

所以刚刚teacher方法中等待的会继续执行,我们来看一下输出结果:

I am teacher , waiting for your homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
I am student, finished one homework
student finish homework
I am teacher, already obtaining student's homework 

event实例对象的对象方法介绍:

  • wait(self, timeout=None):timeout为设置等待的时长,如果超过时长(返回值为False)则不再等待,直接向下执行,如果timeout没有指定则一 直等待,等待的时候是阻塞的没有返回值,;
  • set():如果执行event.set(),将会设置flag为True,那么wait等待的线程就可以向下执行;
  • clear():如果执行event.clear(),将会设置flag标记为Flase, 那么wait等待的线程将再次等待(阻塞);
  • is_set():判断event的flag是否为True,如果为True的话wait等待的线程将向下执行;

3.线程锁

  • Lock是Python中最底层的同步机制,直接由底层模块 thread 实现,每个lock对象只有两种状态,也就是上锁和未上锁;
  • 我们可以通过下面两种方式创建一个Lock对象,注意新创建的 Lock 对象处于未上锁的状态:
thread.allocate_lock()
threading.Lock()
  • 锁是解决临界区资源的问题,保证每一个线程访问临界资源的时候有全部的权利,一旦某个线程获得锁, 其它试图获取锁的线程将被阻塞;
  • locked()方法:用于判断当前是否上锁,如果上锁,返回True,否则返回False;
  • acquire(blocking=True,timeout=-1):表示加锁,默认True为加锁状态(阻塞),False为不阻塞,timeout为设置时长;
  • release()方法:用于释放锁,在完成任务的时候释放锁,让其他的线程获取到临界资源,注意此时必须处于上锁的状态,如果试图释放一个unlocked 的锁,将抛出异常 thread.error;

我们通过两个实例来看一下线程锁的作用:

实例1:
import time
from threading import Thread
homework_list = []

def student(number):
 while len(homework_list) < number: # 满足条件则继续执行下面的代码
 time.sleep(0.001) # 等待0.001秒
 homework_list.append(1)
 print(len(homework_list))

if __name__ == '__main__':
 for i in range(10):
 Thread(target=student, args=(10, )).start() # 同时开启10个线程
 time.sleep(3) # 等待3秒
 print('完成作业的总数为: {}'.format(len(homework_list)))

运行实例1,我们发现最终输出结果完成作业的总数为:19,但是根据代码逻辑来看,应该输出10才对,这是为什么呢?其实是因为在多线程情况下操作临界资源,出现了临界资源争抢的问题,那如何解决这个问题呢,我们来看实例2;

实例2:
import time
import threading
from threading import Thread, Lock

homework_list = []
lock = Lock() # 全局阻塞锁

def student(number):
 while True:
 lock.acquire() # 一定要在获取临界资源之前加锁
 if len(homework_list) >= number: # 满足条件则跳出循环,不满足则继续
 break
 time.sleep(0.001) # 等待0.001秒
 homework_list.append(1)
 lock.release() # 完成任务的时候释放锁,让其他的线程获取到临界资源
 print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list)))


if __name__ == '__main__':
 for i in range(10):
 Thread(target=student, name='student {}'.format(i), args=(10, )).start() # 同时开启10个线程
 time.sleep(3) # 等待3秒
 print('完成作业的总数为: {}'.format(len(homework_list)))

运行实例2发现最终输出完成作业的总数为: 10,是我们想要的结果,实例1和实例2 不同之处在于实例2在获取临界资源之前加了锁,也就是lock.acquire(),我们知道一旦某个线程获得锁, 其它试图获取锁的线程将被阻塞,所以这里不会再发生临界资源争抢的问题了;

4.锁在with语句中的使用

  • 使用with语句加锁with lock,会默认自动释放锁,不需要再使用release()方法来释放锁了,这样可以避免程序中忘记释放锁,更加方便;
import time
import threading
from threading import Thread, Lock

homework_list = []

# 全局阻塞锁
lock = Lock()
def student(number):
 while True:
 with lock:
 if len(homework_list) >= number:
 break
 time.sleep(0.001)
 homework_list.append(1)
 print('current_thread={}, homework_list={}'.format(threading.current_thread().name, len(homework_list)))

if __name__ == '__main__':
 for i in range(10):
 Thread(target=student, name='student {}'.format(i), args=(1000, )).start()
 time.sleep(3)
 print('完成作业的总数为: {}'.format(len(homework_list)))

所以其实这样写,和上面的实例2是一样的效果哟,并且省略了释放锁的步骤,因为with默认自动释放锁,这样就不怕忘记释放锁而导致代码报错了;

5.线程池

接下来学习一下线程池,也就是ThreadPoolExecutor,线程池在构造实例的时候,会传入max_workers参数来设置线程池中最多能同时运行的线程数目;

线程池实例对象有两个非常使用的方法,submit方法和map方法:

  • submit(self, fn, *args, **kwargs):提交线程需要执行的任务(函数名和参数)到线程池中,并返回该任务的句柄,用于提交单个任务;
  • map(self, fn, *iterables, timeout=None, chunksize=1):类似高阶函数map,可以提交任务,且传递一个可迭代对象,返回任务处理迭代对象的结果;
from concurrent.futures import ThreadPoolExecutor
import requests

def fetch_url(url):
 result = requests.get(url=url, )
 return result.text

# 创建10个线程队列的线程池
pool = ThreadPoolExecutor(10)

# 获取任务返回对象
a = pool.submit(fetch_url, 'http://www.baidu.com')

# 取出返回的结果
x = a.result()
print(x) # 获得百度的源码

6.全局解释器锁

  • 尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的,实际上,解释器被一个全局解释器锁保护着 ,它确保任何时候都只有一个Python线程执行;
  • GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势, 就是因为GIL的存在,使得一个进程的多个线程在执行任务的时候,一个CPU时 间片内,只有一个线程能够调度到CPU上运行;
  • 因此CPU密集型的程序一般不会使用Python实现,可以选择Java,GO等语言;
  • 但是对于非CPU密集型程序,例如IO密集型程序,多数时间都是对网络IO的等待,因此Python的多线程完全可以胜任;

对于全局解释器锁的解决方案:

  • 使用multiprocessing创建进程池对象,实现多进程并发,这样就能够使用多CPU计算资源;
  • 使用C语言扩展,将计算密集型任务转移给C语言实现去处理,在C代码实现部分可以释放GIL;

多线程和多进程解决方案:

  • 如果想要同时使用多线程和多进程,最好在程序启动时,创建任何线程之前,先创建一个单例的进程池, 然后线程使用同样的进程池来进行它们的计 算密集型工作,这样类似于线程调用了进程,完成了CPU密集型任务,进程也利用了多CPU的优势;

参考:https://www.9xkd.com/user/plan-view.html?id=4240003103

相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: