由浅入深掌握Python多线程原理与编程步骤

news/2024/5/1 16:58:34/文章来源:https://blog.csdn.net/captain5339/article/details/130040116

由浅入深掌握Python多线程编程

  • 一、 Python多线程编程原理
    • 1. 什么是线程
    • 2. 线程工作原理
    • 3. Python全局锁与线程关系
    • 4. Python 支持多线程的模块
  • 二、由简单的示例初步了解多线程编程步骤
  • 三、标准库 threading 模块介绍
    • 1. threading 模块的主要属性、方法,以及公共函数
      • threading.Thread 对象构造方法
      • threading.Thread类的主要属性
      • threading.Thread类的方法:
    • 2. threading模块提供的公共函数
  • 四、用类的方式来创建多线程
  • 五. 线程生命周期管理
    • 1. 线程管理的基本操作
    • 2. 如何关闭执行耗时任务的线程
    • 3. 主线程如何接收并处理子线程异常
  • 六、同步锁 -- 防止资源同抢
  • 七、高并发编程--线程池技术
    • 1. 创建线程池:
    • 2. 向线程池添加线程
    • 3. 关闭线程池
    • 4. 线程池实例
    • 5. Future 对象介绍

一、 Python多线程编程原理

1. 什么是线程

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。在一个程序中,这些独立运行的程序片段叫作“线程”Thread),利用它编程的概念就叫作“多线程处理”

先通过比较两组概念来加深理解多线程处理。

`并发concurrency`与并行`Parallelism`

• **并发**,是指1个CPU同时启动两个或更多任务。多线程属于并发概念范畴。 • **并行**,是指同时启动两个或更多任务,每个任务运行在各自独立CPU上。 多进程是并行概念范畴。 *注:如果只有1个CPU核,多进程是伪并行,无法执行真正的并行运算。*

如何区别进程与线程?

1个进程有自己的地址空间、内存区、数据堆栈区. 而线程依附于进程,多个线程共享地址空间,指令区,但有自己的寄存区、堆栈指针、内存变量等。 如下图所示。

在这里插入图片描述

2. 线程工作原理

线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其他的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。

几乎每个程序代码运行时,都会有各种I/O操作,由于I/O的完成时间不可确定性,那么程序在等待期间,阻塞了后续任务的执行,降低了程序总体运行速度。这时采用多线程,将等待时间长的任务放在子线程中运行,主线程可以继续执行其它任务。

1个典型的实例是网络服务器程序,可能许多客户同时访问,每条客户连接绝大部分时间其实都是在等待客户端发送请求,服务器进程为每个客户的网络连接开启1个子线程,这样服务器就可以同时处理很多连接,还可以保持良好的实时性。如果没有应用多线程,那么服务器1次只能处理1个客户,其它客户就必须等待,这是无法接受的。

3. Python全局锁与线程关系

什么是全局解释器锁(GIL)
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只允许一个线程在执行,如同单CPU的系统中可以运行多个进程,内存中可以存放多个程序,但在任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器中可以“运行”多个线程,但在任意时刻,只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(global interpreter lock, GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python虚拟机按以下方式执行:

  1. 设置GIL。
  2. 切换到一个线程去运行。
  3. 运行:
    • 指定数量的字节码的指令,
    • 或者 b.线程主动让出控制(如调用time.sleep(0))
  4. 把线程设置为睡眠状态。
  5. 解锁GIL。
  6. 再次重复以上所有步骤。

每个线程轮流执行,每次执行的时间片都非常短。如果某个线程执行时间片内遇到I/O调用, GIL会被释放,以允许其他的线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会用完自己的时间片才归还GIL。
因此,尽管有全局锁的限制,I/O密集型的程序采用多线程编程仍然具备明显优势。

4. Python 支持多线程的模块

Python提供了几个用于多线程编程的模块,包括thread、threading等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理的功能。Python官网推荐使用threading 模块编程。
此外,标准库的 concurrent.future 模块还提供一种异步线程池的实现类ThreadPoolExecutor,适用于高并发的场景。

二、由简单的示例初步了解多线程编程步骤

先通过1个简单示例来演示创建1个线程的实现过程,本示例采用函数式编程方法

  • 首先要定义1个任务函数,本例任务函数带1个参数,
  • 其次使用 threading模块创建1个线程对象,在线程构造方法中,传入任务函数名,以及参数
  • 执行线程
# th_test.py 
import logging
import threading
import time#定义任务函数
def thread_function(name):logging.info("Thread %s: starting", name)time.sleep(2)logging.info("Thread %s: finishing", name)if __name__ == "__main__":format = "%(asctime)s: %(message)s"logging.basicConfig(format=format, level=logging.INFO,datefmt="%H:%M:%S")logging.info("Main    : before creating thread")th_obj = threading.Thread(target=thread_function, args=(1,))logging.info("Main    : before running thread")th_obj.start()logging.info("Main    : wait for the thread to finish")logging.info("Main    : all done")

上述代码中最关键的语句是下面这两句。

th_obj = threading.Thread(target=thread_function, args=(1,))
th_obj.start()

其表示,创建1个线程,执行任务函数 thread_function(), 并传入1个参数。
th_obj.start() 表示启动线程。
下面运行th_test.py ,输出如下:

Main    : before creating thread
Main    : before running thread
Thread 1: starting
Main    : wait for the thread to finish
Main    : all done
Thread 1: finishing

但你可能注意到,主线程打印了" all done" 后,子线程才打印出"finishing", 似乎子线程在主线程之后结束,
再看下面这个例子,我们创建了2个线程,在调用start()方法后,又立即调用join()方法,意味着主线程暂停运行,等待子线程运行结束后再继续。

#!/usr/bin/python
# -*- coding: UTF-8 -*-import threading    #导入threading 模块
import time# 线程函数
def print_time ( threadName ,delay ):count = 0while count < 5:time.sleep(delay)count += 1print("%s: %s \n" % (threadName,time.ctime(time.time())))#线程函数
def print_cube(num):#pring cubeprint("Cube:{} \n".format(num*num*num))# 创建两个线程
if __name__ == "__main__":# create sub threadst1 = threading.Thread( target=print_cube,args=(10,))t2 = threading.Thread( target=print_time,args=("Thread-2",4,))#start threadst1.start()   t2.start()#join 命令:让当前主线程暂停运行,等待子线程运行完t1.join()     t2.join()print("All done") # The statement is executed after sub threads done

三、标准库 threading 模块介绍

1. threading 模块的主要属性、方法,以及公共函数

thread 模块是python 低阶多线程编程接口模块,threading模块则封装了thread, 使用更方便,因此,多线程编程推荐使用threading模块,而threading模块中最重要类为 Thread类。

threading.Thread 对象构造方法

构造方法的格式:
threading.Thread(target=None, name=None, args=(), kwargs={}, daemon=None)
参数说明

  • target 任务函数名称
  • args 元组方式的参数表。
  • kwargs[] 可变参数
  • daemon 是否为后台运行

如下例,将创建1个线程对象,任务函数为myfunc, 传入参数 10, 为后台运行。
th_obj = threading.Thread(myfunc, (10, ), daemon=True )

threading.Thread类的主要属性

  • name 线程名字
  • native_id 线程id(系统分配)
  • daemon bool类型(是否为后台线程)

threading.Thread类的方法:

  • start(), 启动线程
  • join(), 告知主线程等待
  • setName(), 设置线程名
  • getName() 获取线程名
  • get_native_id()
  • is_alive() 线程状态是否为active
  • isDaemon(), setDaemon() 获取、设置后台状态

2. threading模块提供的公共函数

函数是模块提供的公共方法,不属于类方法

threading.active_count(),   #返回当前活动状态的线程对象列表
threading.current_thread()  #返回当前线程对象
threading.get_native_id()   #获取线程id
threading.enumerate()       #返回当前活动状态线程的迭代器
threading.main_thread()     #返回主线程对象

四、用类的方式来创建多线程

创建步骤:
使用threading.Thread创建类,
重写__init__方法和run方法:

示例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-import threading
import timeclass myThread(threading.Thread):  def __init__(self,threadID,name,counter):    # 有3个参数super(myThread,self).__init__()     # 调用父类构造方法self.threadID = threadIDself.name = nameself.counter =counter# 为线程定义一个函数
def print_time ( threadName ,delay ):  #功能方法count = 0while count < 3:time.sleep(delay)count += 1print("%s: %s \n" % (threadName,time.ctime(time.time())))def run(self):                           # 重写run方法print("Starting " + self.name)print_time(self.name,self.counter)   # 调用功能方法print("Exiting " + self.name)th1 = myThread(1,"Thread-1",2 )           # 创建基于类的线程
th1.start() 
th1.join()print("Done -- main thread")

运行结果:

在这里插入图片描述

五. 线程生命周期管理

线程生命周期管理,包含线程启动,状态查询,线程关闭等操作,以及处理线程异常

1. 线程管理的基本操作

线程启动,当线程对象一旦被创建,其活动必须通过调用线程的 start() 方法开始, 在内部会调用该对象的run()方法。

th1 = threading.Thread(taraget=myfunc,name="thread_1", (10,))
th1.start()   #启动线程
th1.join()    #指示主线程暂时挂起,等待子线程运行结束

查看线程状态
一旦线程活动开始,该线程会被认为是 ‘存活的’ 。当它的 run() 方法终结了(不管是正常的还是抛出未被处理的异常),就不是’存活的’。 is_alive() 方法用于检查线程是否存活。

if th1.is_alive():print('Still running')
else:print('Completed')

子线程启动后,关闭方式通常有几种情况:

  1. 子线程启动后,主线程也不等待子线程,子线程运行结束后自行关闭
  2. 子线程启动后,调用 join()方法,主线程等待子线程运行结束。
  3. 子线程是耗时任务,启动后,主线程不等待子线程,但主线程通过全局变量或Event信号来关闭子线程

2. 如何关闭执行耗时任务的线程

思路:在线程内添加状态变量,线程循环时同时检测状态变量,主线程关闭线程时,把状态变量置为False.
示例:

class CountdownTask:def __init__(self):self._running = True   # 定义线程状态变量def terminate(self):self._running = False def run(self, n):# run方法的主循环条件加入对状态变量的判断while self._running and n > 0:print('T-minus', n)n -= 1time.sleep(5)print("thread is ended") c = CountdownTask()
th = Thread(target = c.run, args =(10, ))
th.start()#对于耗时线程,没必要再用join()方法了
# … any code … 
# Signal termination
Q = input("please press any key to quit ")
c.terminate() 

对于函数式编程,可以用全局变量做状态变量,用于通知关闭线程

import threading
import timedef run():while True:print('thread running')global stop_threadsif stop_threads:breakstop_threads = False
t1 = threading.Thread(target = run)
t1.start()
time.sleep(1)
stop_threads = True
t1.join()
print('thread killed')

3. 主线程如何接收并处理子线程异常

threading定义了一些默认异常,如果子线程发生异常,则默认 threading.excepthook回调方法会处理异常,不至于造成主线程退出。如果子线程抛出1个非预期的异常,可能会造成系统终止。
解决方法: 定义1个接收异常的回调函数 exception hook(也可以称为钩子函数),替换系统默认的excepthook。
首先,自定义1个hook函数, 简单地将收到的异常信息打印出来,

# custom exception hook
def custom_hook(args):# report the failureprint(f'Thread failed: {args.exc_value}')

然后在主线程中,将 threading.excepthook 指向自定义的异常处理回调函数,无论收到什么异常都会调用这个函数。

# set the exception hook
threading.excepthook = custom_hook

完整示例代码如下

# example of an unhandled exception in a thread
from time import sleep
import threading# target function that raises an exception
def work():print('Working...')sleep(1)# rise an exceptionraise Exception('Something bad happened')# 自定义异常处理回调函数
def custom_hook(args):# report the failureprint(f'Thread failed: {args.exc_value}')# 将 threading.excepthook 指向自定义的异常处理回调函数
threading.excepthook = custom_hook
# create a thread
thread = threading.Thread(target=work)
# run the thread
thread.start()
# wait for the thread to finish
thread.join()
# continue on
print('Continuing on...')

六、同步锁 – 防止资源同抢

当多个线程同时访问1个数据资源 (如全局变量)时,thread1 在刚修改完,还未读取,马上又被 thread3修改,这时thread1显示了错误的数据。多个线程同时对数据库表进行操作时,也会遇到同样问题。 解决方法是添加同步锁
在这里插入图片描述
Threading 模块提供了Lock 类来解决这个资源同抢问题。 在低层,Lock 使用OS的Semaphore(信号量) object 来实现的。

Lock类操作比较简单:
1) 当线程需要在读写数据前,使用 acquire() 方法加锁,其它线程将无法操作。
2) 使用完成后,使用release() 释放锁。
注意:
如果lock没有正确释放,则其它线程可能永久等待状态而吊死,
示例 :


import threadingx = 0         # 定义1个全局变量 xdef increment():global xx += 1def thread_task(lock):#task for threadfor _ in range(100000):lock.acquire()  # 申请锁,数据不能被其它线程修改了increment()     lock.release()  # 释放锁def main_task():global x# setting global variable x as 0x = 0# creating a locklock = threading.Lock()# creating threadst1 = threading.Thread(target=thread_task, args=(lock,))t2 = threading.Thread(target=thread_task, args=(lock,))# start threadst1.start()t2.start()# wait until threads finish their jobt1.join()t2.join()if __name__ == "__main__":for i in range(10):main_task()print("Iteration {0}: x = {1}".format(i, x))

七、高并发编程–线程池技术

当需要很多任务同时执行时,如果用前面的方法逐个来创建线程,代码量比较大。可以使用线程池来简化操作。
concurrent.futures提供了ThreadPoolExecutor()方法创建线程池,其创建的线程与线程是异步执行的。

注意:不要用ThreadPoolExecutor创建的线程去执行long-run的任务。

1. 创建线程池:

语法:

concurrent.futures.ThreadPoolExecutor( 
max_workers=None, 
thread_name_prefix=“” 
) 

executor = ThreadPoolExecutor(max_workers=10)

2. 向线程池添加线程

方式一:通过map() 将任务线程加入线程池

# call a function on each item in a list and process results
# items is parameter tuple list 
for result in executor.map(task, items):# process result...

方式二: 通过submit提交
每一次submit,ThreadPoolExecutor会返回1个future object。
这时task可能执行完,也可能还没执行,可以通过future对象 来查询 任务状态,也可以获得执行结果。

# submit a task to the pool and get a future immediately
future = executor.submit(task, item)
# get the result once the task is done
result = future.result()

在executor.submit()返回的future 对象非常有用,后有介绍。

3. 关闭线程池

线程池执行后,可以用shutdown()方法关闭

# shutdown the thread pool
executor.shutdown()

也可以使用上下文方式,线程池会自动关闭

..
# create a thread pool
with ThreadPoolExecutor(max_workesr=10) as executor:# call a function on each item in a list and process resultsfor result in executor.map(task, items):# process result...# ...
# shutdown is called automatically

4. 线程池实例

import requests
import time
from concurrent.futures import ThreadPoolExecutorimg_urls =  ['https://media.example.com/wp-content/uploads/20190623210949/download21.jpg','https://media.example.com/wp-content/uploads/20190623211125/d11.jpg','https://media.example.com/wp-content/uploads/20190623211655/d31.jpg','https://media.example.com/wp-content/uploads/20190623212213/d4.jpg','https://media.example.com/wp-content/uploads/20190623212607/d5.jpg','https://media.example.com/wp-content/uploads/20190623235904/d6.jpg',
]t1 = time.perf_counter()
def download_image(img_url):img_bytes = requests.get(img_url).contentprint("Downloading...")# Fetching images concurrently thus speeds up the download.
with ThreadPoolExecutor(10) as executor:executor.map(download_image, img_urls)t2 = time.perf_counter()
print(f'MultiThreaded Code Took:{t2 - t1} seconds')

output:

Downloading...
Downloading...
Downloading...
Downloading...
Downloading...
Downloading...
MultiThreaded Code Took:1.7290295 seconds

5. Future 对象介绍

主线程可通过Future 对象管理子线程的状态及控制,
Future对象由Executor.submit()提交后返回的1个对象,其主要方法:
cancel() 中止线程
cancelled(), 如果线程已中止,返回true,
running() 返回运行状态
done() Return True if the call was successfully cancelled or finished running
result() 线程返回值
add_done_callback(fn), 添加1个回调函数
exception() 线程产生的异常

使用示例 :

with ThreadPoolExecutor(max_workers=1) as executor:future = executor.submit(pow, 323, 1235)print(future.result())

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_286151.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ [图论算法详解] 欧拉路欧拉回路

蒟蒻还在上课&#xff0c;所以文章更新的实在慢了点 那今天就来写一篇这周刚学的欧拉路和欧拉回路吧 讲故事环节&#xff1a; 在 一个风雪交加的夜晚 18世纪初普鲁士的哥尼斯堡&#xff0c;有一条河穿过&#xff0c;河上有两个小岛&#xff0c;有七座桥把两个岛与河岸联系…

Python手写板 画图板 签名工具

程序示例精选 Python手写板 画图板 签名工具 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<Python手写板 画图板 签名工具>>编写代码&#xff0c;代码整洁&#xff0c;规则&am…

Diffusion模型系列文章

DDPM 论文 扩散模型包括两个过程&#xff1a;前向过程&#xff08;forward process&#xff09;和反向过程&#xff08;reverse process&#xff09;&#xff0c;其中前向过程又称为扩散过程&#xff08;diffusion process&#xff09;&#xff0c;如下图所示&#xff0c;从x…

如何定位Spark数据倾斜问题,解决方案

文章目录前言一、数据倾斜和数据过量二、 数据倾斜的表现三、定位数据倾斜问题定位思路&#xff1a;查看任务-》查看Stage-》查看代码四、7种典型的数据倾斜场景解决方案一&#xff1a;聚合元数据解决方案二&#xff1a;过滤导致倾斜的key解决方案三&#xff1a;提高shuffle操作…

1.docker-安装及使用

1.安装步骤 Install Docker Engine on CentOS 1. 确定CenOS7及以上版本 cat /etc/redhat-release2.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine3.yum安…

Spimes x5.0主题模板全开源源码/Typecho主题模板

☑️ 品牌&#xff1a;Typecho ☑️ 语言&#xff1a;PHP ☑️ 类型&#xff1a;主题模板 ☑️ 支持&#xff1a;PCWAP &#x1f389;有需要的朋友记得关赞评&#xff0c;底部分享获取&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 Spimes x5.0主题模板全开…

基于overleaf 的美国大学生数学建模竞赛(美赛)latex 格式模板(含信件和附件)

可能是最后一次打美赛了&#xff0c;感觉有的东西不整理整理有点对不起自己的经历。感觉为这个比赛付出过挺多的&#xff0c;这几次参赛的经历也从各种方面提升了我的能力&#xff0c;相信未来的自己也还会怀念这段时光。 个人认为美赛的难点之一就是优质资源难得&#xff0c;…

Pytorch深度学习笔记(三)线性模型

目录 1.机械学习的过程 2.线性模型 1.机械学习的过程 机械学习的过程&#xff1a; 1.准备数据集DataSet——>2.选择模型Model——>3.训练Training——>4.推理Infering 监督学习&#xff1a;用已知标签的训练样本训练模型&#xff0c;用来预测未来输入样本的标签&#…

Android---内存泄漏检测核心原理

目录 LeakCanary 核心原理 LeakCanary 检测对象的类型 ReferenceQueue 与 WeakReference LeakCanary 里的监控列表与保留列表 常见内存泄漏案例 1. 单例导致内存泄漏 2. 静态变量导致内存泄漏 3. 非静态内部类导致内存泄漏 4. 未取消注册或回调导致内存泄漏 5. Timer…

ChatGPT的发展对客户支持能提供什么帮助?

多数组织认为客户服务是一种开销&#xff0c;实际上还可以将客户服务看成是一种机会。它可以让你在销售后继续推动客户的价值。成功的企业深知&#xff0c;客户服务不仅可以留住客户&#xff0c;还可以增加企业收入。客户服务是被低估的手段&#xff0c;它可以通过推荐、见证和…

AI绘画与虚拟人生成实践(一):生成人像,AI绘画模型和工具的效果对比

本篇的目的是生成一个虚拟的女生形象。先进入正题说明人像怎么生成,本篇使用到的工具和工具的介绍放在文末。 先来一波Midjourney生成的美图提升下大家学习的欲望 以上四张图使用的是相同的Prompt,如下: a beautiful chinese girl, 18 years old, detailed and big eye…

【c++初阶】命名空间的定义

命名空间的定义一.缺陷二.namespace和::三.访问namespace四.一些注意1.工程里标准库的展开2.命名域的小技巧一.缺陷 在c语言中&#xff0c;如果我们同时定义一个全局变量和一个局部变量并且使用同一个名称的话&#xff0c;是可以编过的&#xff08;因为全局和局部是属于两个不同…

算法训练Day25:216.组合总和III ,17.电话号码的字母组合

文章目录[组合总和 III](https://leetcode.cn/problems/combination-sum-iii/description/)题解电话号码的字母组合题解组合总和 III CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsMedium (71.84%)6570--0 TagsCompanies 找出所有相加之和为 n 的 …

分子生物学 第五章 DNA损伤修复和突变

文章目录第五章 DNA损伤修复和突变第一节第二节 DNA损伤的类型1 造成DNA损伤的因素2 DNA损伤的类型3 DNA损伤修复机制3.1 直接修复3.2 切除修复3.3 双链断裂修复3.4 重组修复3.5 跨越合成第五章 DNA损伤修复和突变 第一节 损伤&#xff1a;比如碱基&#xff0c;甲基化 突变&…

JavaWeb——锁策略, cas和synchronized优化过程

目录 一、锁策略 1、悲观锁和乐观锁 2、轻量级锁和重量级锁 3、自旋锁和挂起等待锁 4、互斥锁和读写锁 5、可重入锁和不可重入锁 6、公平锁和非公平锁 二、cas和synchronized 优化过程 1、CAS&#xff08;compare and swap&#xff09; &#xff08;1&#xff09;、原…

路由器的两种工作模式及快速通过express搭建微型服务器流程,解决刷新页面服务端404的问题

history模式与hash模式 首先这个#叫做hash&#xff0c;最大的特点就是不会随的http请求&#xff0c;发给服务器。 默认的模式是hash模式&#xff0c;如果想要修改&#xff0c;可以在router里面的index.js中配置mode属性&#xff0c; 它们俩直接的区别最明面上的有没有#和hist…

类型转换——C++

1. C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者返回值类型与接收返回值类型不一致时&#xff0c;就需要发生类型转化&#xff0c; C语言中总共有两种形式的类型转换&#xff1a;隐式类型转换…

linux工具gcc/g++/gdb/git的使用

目录 gcc/g 基本概念 指令集 函数库 &#xff08;重要&#xff09; gdb使用 基本概念 指令集 项目自动化构建工具make/makefile 进度条小程序 ​编辑 git三板斧 创建仓库 git add git commit git push git status git log gcc/g 基本概念 gcc/g称为编译器…

[ 应急响应基础篇 ] evtx提取安全日志 事件查看器提取安全日志

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Java阶段一Day22

Java阶段一Day22 文章目录Java阶段一Day22线程安全synchronized教师总结新单词多线程多线程并发安全问题概念例synchronized关键字同步方法同步块在静态方法上使用synchronized互斥锁总结重点:多线程并发安全问题聊天室(续)实现服务端发送消息给客户端服务端转发消息给所有客户…