Python 多线程
文章目录
- 一、简介
- 1.1 多线程的特性
- 1.2 GIL
- 二、线程
- 1.2 单线程
- 1.3 多线程
- 三、线程池
- 3.1 pool.submit
- 3.2 pool.map
- 四、Lock(线程锁)
- 4.1 无锁导致的线程资源异常
- 4.2 有锁
- 五、Event(事件)
- 5.1 简介
- 5.2 示例
- 六、Queue(队列)
- 6.1 简介
- 6.2 生产者 & 消费者
- 七、Condition(条件锁)
- 7.1 简介
- 7.2 notify 单任务通信
- 7.3 notify_all 多任务通信
- 八、Semaphore(信号量)
- 8.1 简介
- 8.2 示例
一、简介
说起python线程,说少也少,比如线程怎么启动,获取结果,阻塞等;还有线程池的两种运行方式以及使用的一些案例。说多的话,又会涉及到Lock,Rlock,Queue,Condition,Event等很多东西。
这边博客先留着慢慢写,主要介绍线程和线程池的使用,后面吧所有线程内部的东西都深入说一下,为什么要用到这些。
1.1 多线程的特性
Python多线程是Python的一个重要特性,它允许程序同时执行多个线程。Python中的线程是轻量级的,它们共享内存空间,因此创建和销毁线程的开销很小。
多线程切换的效率是以时间为指标的,因为线程切换需要保存当前线程的状态并加载下一个线程的状态,这个过程需要花费一定的时间。在多线程切换的过程中,如果线程的数量过多,那么线程切换的时间就会占用大量的CPU时间,从而导致程序的执行效率降低。因此,在编写多线程程序时,需要合理的控制线程的数量,避免线程切换的时间过长。
1.2 GIL
Python GIL (Global Interpreter Lock)是Python解释器的一个特性,它是一种互斥锁,用于保护Python解释器的内部数据结构。在任何时刻,只有一个线程可以执行Python字节码。这意味着,即使在多核CPU上运行Python程序,也只能使用一个核。
这个特性对于CPU密集型任务来说是一个瓶颈,因为它不能充分利用多核CPU的优势。但是对于I/O密集型任务来说,Python GIL并不是一个问题,因为在I/O操作期间,Python解释器会释放GIL,以便其他线程可以执行Python字节码。如果你想充分利用多核CPU,可以使用多进程或者使用其他语言编写CPU密集型任务的代码。
二、线程
1.2 单线程
import threading
import requestsdef task(url):resp = requests.get(url=url).textprint(len(resp))thread = threading.Thread(target=task, args=('https://www.baidu.com', ))
thread.start() # 启动这个线程
thread.join() # 阻塞线程print('end')
1.3 多线程
这里有个点记一下,join是会阻塞任务的,只有当线程全部都跑完,才会向下执行;如果你需要在执行线程的时候响应外部请求,那么只start即可。
import threading
import requestsdef task(url):resp = requests.get(url=url).textprint(f'url length: ', len(resp))urls = [f'https://www.raycloud.com/r/cms/www/default/images/clientele_logo/logo_{num}.png' for num in range(1, 6)]
threads = []
for url in urls:thread = threading.Thread(target=task, args=(url, ))thread.start()threads.append(thread)for thread in threads:thread.join()
三、线程池
线程池主要是解决多任务并发的实效性问题,简单的说就是让任务跑的很快,系统资源利用率更高。线程池对于运行多个线程的优点主要就是线程的启动和关闭有资源的开销,而线程池则可以复用线程。你就当作TCP中客户端TIME-WAIT状态的连接重新用于新的TCP连接,一个道理。本质都是为了减少资源开销。
那为啥要用线程池,而不用进程池,区别就是
- 线程池中的线程启动开销更低,切换也更快
- 线程池主要是为了解决IO的问题(文件IO,网络IO等)
- 进程池主要是为了解决CPU密集的问题(数据运算,数据处理等)
python中线程池的库是concurrent.futures,线程池有两种使用方式,submit和map
3.1 pool.submit
下面是我业务场景中要通过线程池启动两个线程,并且要将返回的值拿过来对用户进行响应
一个是获取阿里云SLB的QPS
一个是获取阿里云SLB的RT
这个案例很好说明为什么我要用线程池中的 pool.submit 方法而不是 pool.map,结论是都能实现,submit更方便
- 我的任务很少,不去太考虑线程的重用和开销问题
- 明确知道需要两个指标,一个是QPS,一个是RT
- pool.submit的结果可以通过result()直接拿到
- pool.map 返回的是一个generator对象,用起来会更麻烦
# submit获取结果
qps = resp_qps.result()
rt = resp_rt.result()# map获取结果
results = pool.map(func, data)
for result in results:print(result)
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor()def get_slb_metric(ali_obj, metric_name, delay, dimensions):""" 获取阿里云指标数据 """period = 60namespace = 'acs_slb_dashboard'timestamp = int(time.time())start_time = timestamp_to_str(timestamp=timestamp - delay - period)end_time = timestamp_to_str(timestamp=timestamp - delay)response = ali_obj.describe_metric_data_request(namespace=namespace,metric_name=metric_name,start_time=start_time,end_time=end_time,period=period,dimensions=dimensions,)return json.loads(response['Datapoints'])def get_slb_metric_data(ali_obj, delay, dimensions):""" 获取阿里云指标数据公共方法 """resp_metric = {'success': False,'msg': None,'retry': 0,'data': {'qps': None,'rt': None,}}while resp_metric['retry'] < 5:resp_metric['retry'] += 1resp_qps = pool.submit(get_slb_metric, ali_obj, 'Qps', delay, dimensions)resp_rt = pool.submit(get_slb_metric, ali_obj, 'Rt', delay, dimensions)qps = resp_qps.result()rt = resp_rt.result()if qps and rt:resp_metric['success'] = Trueresp_metric['data']['qps'] = qps[0]['Average']resp_metric['data']['rt'] = rt[0]['Average']return resp_metricdelay += 10resp_metric['msg'] = f'请求异常: \n\n qps或rt数据为空,请延长周期,最后一次delay为: {delay}s, 递增10s请求5次,未成功获取到数据'return resp_metric
3.2 pool.map
那什么时候用pool.map,以我的习惯是当这个任务是固定的,在批量处理数据的情况下,pool.map特别方便。
- 需要处理的任务非常多,需要考虑线程开销且复用
- 对于任务的执行结果不需要按类型来区分,批量获取
那具体用在什么场景下呢?下面是我一个业务场景,ECI配置中心,里面记录了一条条项目的配置信息,且ECI项目的POD数量是动态的,里面记录了获取POD的URL,需要实时请求。eci_list的方法需要读取配置中心,
这个时候我的任务数是不固定的,有多少个业务需要ECI,那就有多少个URL需要去请求,我总不能一个一个手动去创建线程,并且,所有业务URL的接口给我返回的数据格式是固定的,我也只会取固定的值,那么pool.map就非常方便了。批量请求接口,批量处理结果。
def get_pod_info(data):# 获取项目信息resp_data = {'success': False,'msg': None,'data': data}try:url = data.get('url')headers = {'contene-type': 'application/json'}project = data.get('project')profile = data.get('profile')sign = data.get('sign')req_data = {'sign': sign,'project': project,'profile': profile}resp = requests.post(url=url, headers=headers, data=json.dumps(req_data))if resp.status_code == 200:resp_data['data']['pod'] = 100resp_data['data']['open'] = Trueresp_data['success'] = Trueelse:resp_data['msg'] = f'ECI业务接口异常: {resp.text}\n\n地址: {url}\n\n项目: {project}\n\n标签: {profile}'except Exception as err:resp_data['msg'] = f'ECI运维接口异常: {err}\n\n地址: {url}\n\n项目: {project}\n\n标签: {profile}'finally:return resp_datadef eci_list():if request.method == 'GET':resp_data = {'success': False,'msg': None,'data': None}try:# 获取项目列表queryset = OpsEci.query.all()data = list()for obj in queryset:_obj_host = CloudHost.query.filter_by(resource_type='kubernetes', instance_id=obj.kubernetes).first()_kube_host = re.search('\d+\.\d+\.\d+\.\d+', _obj_host.private_ip[0]).group()_data = {'project': obj.project,'profile': obj.profile,'kubeconfig': f"/home/tomcat/.kube/kubeconfig/{_kube_host}",'token': obj.token,'address': obj.address,'url': obj.url,'sign': obj.sign}data.append(_data)# 处理项目列表with ThreadPoolExecutor() as pool:results = pool.map(get_pod_info, data)results_list = list()for result in results:project = result['data']['project']profile = result['data']['profile']app = project if profile == 'jst' else f'{project}-{profile}'if result.get('success'):results_list.append({'status': True,'open': result['data']['open'],'pod': result['data']['pod'],'ding_token': result['data']['token'],'app': app,'kubeconfig_path': result['data']['kubeconfig']})else:results_list.append({'status': False,'app': app,})resp_data['success'] = Trueresp_data['data'] = results_listexcept Exception as err:resp_data['msg'] = f'获取ECI项目信息异常: {err}'finally:return resp_data
四、Lock(线程锁)
4.1 无锁导致的线程资源异常
当线程没有锁以后,不同的线程使用共享资源会出现不可预估的后果
我们期望的情况
王二狗第1次取钱
王二狗第2次取钱
取钱成功, 剩余: 200
取钱失败: 余额: 200
可能会出现的情况
王二狗第1次取钱
王二狗第2次取钱
取钱成功, 剩余: 200
取钱成功, 剩余: -600
无锁代码,这里为了能稳定复现,特别加了sleep
from concurrent.futures import ThreadPoolExecutor
import threading
import timepool = ThreadPoolExecutor()def bank(amount):global balanceprint(f'王二狗第{threading.current_thread().name}次取钱')if balance > amount:time.sleep(0.1)balance = balance - amountprint('取钱成功, 剩余: ', balance)else:print('取钱失败: 余额: ', balance)if __name__ == '__main__':balance = 1000t1 = threading.Thread(target=bank, args=(800,), name='1')t2 = threading.Thread(target=bank, args=(800,), name='2')t1.start()t2.start()
4.2 有锁
如果在使用线程共享资源的时候,给资源加上锁,那么我们每次运行的结果都是一致的
王二狗第1次取钱
王二狗第2次取钱
取钱成功, 剩余: 200
取钱失败: 余额: 200
有锁代码,在操作线程共享资源的时候,给资源上锁
import threading
import timepool = ThreadPoolExecutor()
lock = threading.Lock()def bank(amount):global balanceprint(f'王二狗第{threading.current_thread().name}次取钱')with lock:if balance > amount:time.sleep(0.1)balance = balance - amountprint('取钱成功, 剩余: ', balance)else:print('取钱失败: 余额: ', balance)if __name__ == '__main__':balance = 1000t1 = threading.Thread(target=bank, args=(800,), name='1')t2 = threading.Thread(target=bank, args=(800,), name='2')t1.start()t2.start()
五、Event(事件)
5.1 简介
Event是python中的一个同步原语,用于线程之间的通信。event有两种状态,分别是set和clear。当event处于set状态时,调用wait方法的线程会立即返回,否则会一直阻塞,直到event被set。
5.2 示例
下面例子中,创建了一个event事件,两个人worker。worker_b调用了event.wait()方法,这会使worker线程阻塞,直到event被set。当worker_a开始运行后,将event置为set后,worker_b结束阻塞,开始运行。
import threading
import timeevent = threading.Event()def worker_a():print(f'{time.time()}: worker_a 等待运行')print(f'{time.time()}: worker_a 开始运行')event.set()def worker_b():print(f'{time.time()}: worker_b 等待运行')event.wait()print(f'{time.time()}: worker_b 开始运行')if __name__ == '__main__':t_a = threading.Thread(target=worker_a)t_b = threading.Thread(target=worker_b)t_b.start()time.sleep(1)t_a.start()
执行结果
1679301511.736171: worker_b 等待运行
1679301512.741683: worker_a 等待运行
1679301512.741767: worker_a 开始运行
1679301512.741956: worker_b 开始运行
六、Queue(队列)
6.1 简介
Python中的Queue模块提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后进先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。
6.2 生产者 & 消费者
下面是一个使用Queue模块实现多线程的示例,其中包括了生产者和消费者两个线程,生产者向队列中添加元素,消费者从队列中取出元素,而他们都是用队列queue.Queue();除此之外,还有queue.LifoQueue,queue.PriorityQueue。
import threading
import queue
import timeclass Producer(threading.Thread):def __init__(self, queue):threading.Thread.__init__(self)self.queue = queuedef run(self):for i in range(100):self.queue.put(i)time.sleep(0.3)print('producer put end')class Consumer(threading.Thread):def __init__(self, queue):threading.Thread.__init__(self)self.queue = queuedef run(self):while True:if self.queue.empty():time.sleep(1)print('queue is empty, waiting ...')else:print(f'consumer get {self.queue.get()}')time.sleep(0.1)if __name__ == '__main__':q = queue.Queue()producer = Producer(q)consumer = Consumer(q)producer.start()consumer.start()
七、Condition(条件锁)
7.1 简介
在 Python 中,可以使用 threading.Condition 实现条件锁。Condition 对象提供了 acquire() 和 release() 方法,与 Lock 对象的方法类似。此外,Condition 对象还提供了 wait()、notify() 和 notify_all() 方法,用于线程间的协调。具体使用方法可以参考 Python 官方文档中的 threading.Condition 部分。
在 threading.Condition 中,wait() 方法会释放锁并挂起当前线程,直到另一个线程调用 notify() 或 notify_all() 方法唤醒它。notify() 方法会随机唤醒一个挂起的线程,而 notify_all() 方法会唤醒所有挂起的线程。需要注意的是,wait() 方法只能在已经获得锁的情况下调用,否则会抛出 RuntimeError 异常。
在使用 threading.Condition 时,通常需要先获得一个 Lock 对象,然后使用这个 Lock 对象创建一个 Condition 对象。在需要等待某个条件时,调用 Condition 对象的 wait() 方法;在满足条件时,调用 notify() 或 notify_all() 方法唤醒等待的线程
7.2 notify 单任务通信
import threading
import timecondition = threading.Condition()class Master(threading.Thread):"""主任务类,执行过后等待子任务响应"""def __init__(self, name, condition):super().__init__(name=name)self.name = nameself.cond = conditiondef run(self):with self.cond:print(self.name, '-----任务开始-----')print(self.name, '事件A处理完毕, 等待worker响应...')self.cond.notify()self.cond.wait()print(self.name, '事件B处理完毕, 等待worker响应...')self.cond.notify()self.cond.wait()print(self.name, '事件C处理完毕, 等待worker响应...')self.cond.notify()self.cond.wait()print(self.name, '事件D处理完毕, 等待worker响应...')self.cond.notify()self.cond.wait()print(self.name, '-----任务结束-----')class Worker(threading.Thread):"""子任务类,等待主任务通知并响应"""def __init__(self, name, condition):super().__init__(name=name)self.name = nameself.cond = conditiondef run(self):with self.cond:self.cond.wait()print(self.name, '事件A已响应, 请继续')self.cond.notify()self.cond.wait()print(self.name, '事件B已响应, 请继续')self.cond.notify()self.cond.wait()print(self.name, '事件C已响应, 请继续')self.cond.notify()self.cond.wait()print(self.name, '事件D已响应, 请继续')self.cond.notify()if __name__ == '__main__':master = Master('master', condition)worker = Worker('worker', condition)worker.start()time.sleep(1)master.start()
执行结果
master -----任务开始-----
master 事件A处理完毕, 等待worker响应...
worker 事件A已响应, 请继续
master 事件B处理完毕, 等待worker响应...
worker 事件B已响应, 请继续
master 事件C处理完毕, 等待worker响应...
worker 事件C已响应, 请继续
master 事件D处理完毕, 等待worker响应...
worker 事件D已响应, 请继续
master -----任务结束-----
7.3 notify_all 多任务通信
import threading
import timecondition = threading.Condition()class Master(threading.Thread):"""主任务类,执行过后等待子任务响应"""def __init__(self, name, condition):super().__init__(name=name)self.name = nameself.cond = conditiondef run(self):with self.cond:print(self.name, '前置准备工作结束, 通知子任务开始任务...')time.sleep(1)self.cond.notify_all()class Worker(threading.Thread):"""子任务类,等待主任务通知并响应"""def __init__(self, name, condition):super().__init__(name=name)self.name = nameself.cond = conditiondef run(self):with self.cond:print(self.name, '准备就绪, 等待调度...')self.cond.wait()print(self.name, '接收到主任务通知, 开始执行任务')print(self.name, '任务A执行完成')print(self.name, '任务B执行完成')print(self.name, '任务C执行完成')if __name__ == '__main__':master = Master('master', condition)worker_a = Worker('worker-a', condition)worker_b = Worker('worker-b', condition)worker_c = Worker('worker-c', condition)worker_a.start()worker_b.start()worker_c.start()time.sleep(0.3)master.start()
worker-a 准备就绪, 等待调度...
worker-b 准备就绪, 等待调度...
worker-c 准备就绪, 等待调度...
master 前置准备工作结束, 通知子任务开始任务...
worker-a 接收到主任务通知, 开始执行任务
worker-a 任务A执行完成
worker-a 任务B执行完成
worker-a 任务C执行完成
worker-b 接收到主任务通知, 开始执行任务
worker-b 任务A执行完成
worker-b 任务B执行完成
worker-b 任务C执行完成
worker-c 接收到主任务通知, 开始执行任务
worker-c 任务A执行完成
worker-c 任务B执行完成
worker-c 任务C执行完成
八、Semaphore(信号量)
8.1 简介
Semaphore用于控制对共享资源的访问。semaphore维护一个内部计数器。该计数器可以通过 acquire() 和 release() 两个方法来增加和减少。当计数器为0时,acquire() 方法将会被阻塞,直到其他线程调用 release() 方法位置。
semaphore.acquire() 将会使计数器-1,当计数器为0则会阻塞当前线程
semaphore.release() 将会时计数器+1,以便有更多的资源去使用计数器
8.2 示例
下面模拟10个任务运行的情况,同时运行三个线程,通过Semaphore进行控制线程数。可以发现,通过semaphore即可控制线程的worker
import threading
import timesemaphore = threading.Semaphore(3)def task():"""任务机"""with semaphore:print(time.strftime('%H:%M:%S'), threading.current_thread().name, '开始执行...')time.sleep(2)if __name__ == '__main__':for i in range(10):t = threading.Thread(target=task)t.start()
执行结果
18:12:05 Thread-1 开始执行...
18:12:05 Thread-2 开始执行...
18:12:05 Thread-3 开始执行...
18:12:07 Thread-4 开始执行...
18:12:07 Thread-5 开始执行...
18:12:07 Thread-6 开始执行...
18:12:09 Thread-7 开始执行...
18:12:09 Thread-9 开始执行...
18:12:09 Thread-8 开始执行...
18:12:11 Thread-10 开始执行...
相关文章:
Python 多线程
文章目录一、简介1.1 多线程的特性1.2 GIL二、线程1.2 单线程1.3 多线程三、线程池3.1 pool.submit3.2 pool.map四、Lock(线程锁)4.1 无锁导致的线程资源异常4.2 有锁五、Event(事件)5.1 简介5.2 示例六、Queue(队列&a…...
JVM笔记(九)选择合适的垃圾收集器
Epsilon收集器Epsilon收集器由RedHat公司在JEP 318中提出,在此提案里Epsilon被形容成一个无操作的收集器(A No-Op Garbage Collector),而事实上只要Java虚拟机能够工作,垃圾收集器便不可能是真正“无操作”的。原因是“…...
二维图像处理到三维点云处理
一、Opencv和PCL 下面是opencv和pcl的特点、区别和联系的详细对比表格。 特点/区别/联系OpenCVPCL英文全称Open Source Computer Vision LibraryPoint Cloud Library语言C、Python、JavaC功能图像处理(图像处理和分析、特征提取和描述、图像识别和分类、目标检测和跟踪等)、计…...
leetcode 删除有序数组中的重复项
题目 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一…...
JVM学习.03 类加载机制
1、前言从事Java开发工作的都知道,Java程序提交到JVM运行时,需要编译成Class文件,才能被JVM加载运行。那么这些Class文件进入到虚拟机后会发生什么?以及Class是如何被加载的?这些都是本文要讲解的部分。2、类加载时机所…...
Celery使用:优秀的python异步任务框架
目录Celery 简介介绍安装基本使用Flask使用Celery异步任务定时任务Celery使用Flask上下文进阶使用参考停止Worker后台运行Celery 简介 介绍 Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。 它是一个…...
第十四届蓝桥杯三月真题刷题训练——第 19 天
第 1 题:灌溉_BFS板子题 题目描述 小蓝负责花园的灌溉工作。 花园可以看成一个 n 行 m 列的方格图形。中间有一部分位置上安装有出水管。 小蓝可以控制一个按钮同时打开所有的出水管,打开时,有出水管的位置可以被认为已经灌溉好。 每经过一分…...
类和对象 - 下
本文已收录至《C语言》专栏! 作者:ARMCSKGT 目录 前言 正文 初始化列表 成员变量的定义与初始化 初始化列表的使用 变量定义顺序 explicit关键字 隐式类型转换 自定义类型隐式转换 explicit 限制转换 关于static static声明类成员 友元 友…...
【云原生】Linux基础IO(文件理解与操作)
✨个人主页: Yohifo 🎉所属专栏: Linux学习之旅 🎊每篇一句: 图片来源 🎃操作环境: CentOS 7.6 阿里云远程服务器 Great minds discuss ideas. Average minds discuss events. Small minds disc…...
CentOS 7 安装 mysql 8.0 客户端
只想安装 mysql-client 8.0 , 结果发现直接 yum install mysql mysql-client 安装的版本是 mysql Ver 15.1 Distrib 5.5.68-MariaDB ,这个版本太低,连接其他服务器上的 mysql 8.0 时总是失败,因为 mysql 8.0 加密方式改变了&#…...
Ubuntu下载、配置、安装和编译opencv
1 安装相关依赖安装opencv前,需要先准备好编译器、相关依赖sudo apt-get install gcc g cmake vim sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev sudo apt-get install libgtk2.0-…...
第七讲 贪心
文章目录股票买卖 II货仓选址(贪心:排序中位数)糖果传递(❗贪心:中位数)雷达设备(贪心排序)付账问题(平均值排序❓)乘积最大(排序/双指针)后缀表达…...
数字藏品的未来及发展趋势
随着互联网的普及以及数字文化的日益发展,数字藏品作为一种全新的收藏方式正在逐步兴起。数字藏品可以是数字版权、数字艺术品、数字音乐以及数字视频等形式,这些藏品通过数字化技术保存下来,并在互联网上进行传播和交易。数字藏品的发展趋势…...
值得记忆的STL常用算法,分分钟摆脱容器调用的困境,以vector为例,其余容器写法类似
STL常用算法 概述: 算法主要是由头文件<algorithm> <functional> <numeric>组成 <algorithm>是所有STL头文件中最大的一个,范围涉及到比较、交换、查找、遍历操作、复制、修改等等 <nuneric>体积很小,只包括…...
java如何手动导jar包
今天用IDEA,需要导入一个Jar包,因为以前都是用eclipse的,所以对这个idea还不怎么上手,连打个Jar包都是谷歌了一下。 但是发现网上谷歌到的做法一般都是去File –> Project Structure中去设置,有没有如同eclipse一样…...
怎么防止SQL注入?
首先SQL注入是一种常见的安全漏洞,黑客可以通过注入恶意代码来攻击数据库和应用程序。以下是一些防止SQL注入的基本措施: 数据库操作层面 使用参数化查询:参数化查询可以防止SQL注入,因为参数化查询会对用户输入的数据进行过滤和…...
【千题案例】TypeScript获取两点之间的距离 | 中点 | 补点 | 向量 | 角度
我们在编写一些瞄准、绘制、擦除等功能函数时,经常会遇到计算两点之间的一些参数,那本篇文章就来讲一下两点之间的一系列参数计算。 目录 1️⃣ 两点之间的距离 ①实现原理 ②代码实现及结果 2️⃣两点之间的中点 ①实现原理 ②代码实现及结果 3…...
堆叠注入--攻防世界CTF赛题学习
在一次联系CTF赛题中才了解到堆叠注入,在这里简单介绍一下。 堆叠注入的原理什么的一搜一大堆,我就不引用百度了,直接进入正题。 这个是攻防世界的一道CTF赛题。 采用寻常思路来寻找sql注入漏洞。 payload:1 and 11-- 利用payload: and 12…...
STM32 ADC+定时器+DMA+FFT
本次实现的功能为单片机DAC输出一个正弦波,然后ADC定时采样用DMA输出,最后对DAC输出的波形进行FFT。单片机STM32F103ZET6内部时钟一、配置ADCADC端口为PA1,采用DMA输出,定时器3触发定时器时钟64M,分频后为102.4KHzADC采…...
用Node.js实现一个HTTP服务器程序(文件服务器)
http Node.js开发的目的就是为了用JavaScript编写Web服务器程序。因为JavaScript实际上已经统治了浏览器端的脚本,其优势就是有世界上数量最多的前端开发人员。如果已经掌握了JavaScript前端开发,再学习一下如何将JavaScript应用在后端开发,就是名副其实的全栈了。 HTTP协…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...
【SpringBoot自动化部署】
SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一,能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时,需要添加Git仓库地址和凭证,设置构建触发器(如GitHub…...
