实现一个简易动态线程池

项目完整代码:https://github.com/YYYUUU42/Yu-dynamic-thread-pool

如果该项目对你有帮助,可以在 github 上点个 ⭐ 喔 🥰🥰

1. 线程池概念

2. ThreadPoolExecutor 介绍

2.1. ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的

2.2. 任务执行流程

3. 为什么需要动态线程池

4. 动态化线程池

4.1. 整体设计

4.2. 流程图

5. 基于 Redis 实现

5.1. 为什么使用Redis的发布订阅

5.2. 具体实现流程

5.3. 测试


1. 线程池概念

线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如Tomcat。

线程过多会带来额外的开销,其中包括创建销毁线程的开销、调度线程的开销等等,同时也降低了计算机的整体性能。线程池维护多个线程,等待监督管理者分配可并发执行的任务。这种做法,一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

  • 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
  • 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
  • 系统无法合理管理内部的资源分布,会降低系统的稳定性。

为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。

使用线程池好处

  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

2. ThreadPoolExecutor 介绍

2.1. ThreadPoolExecutor是如何运行,如何同时维护线程和执行任务的

线程池在内部实际上构建了一个生产者消费者模型,将线程和任务两者解耦,并不直接关联,从而良好的缓冲任务,复用线程。线程池的运行主要分成两部分:任务管理、线程管理。

任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转:

  1. 直接申请线程执行该任务;
  2. 缓冲到队列中等待线程执行;
  3. 拒绝该任务。线程管理部分是消费者,它们被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续获取新的任务去执行,最终当线程获取不到任务的时候,线程就会被回收。

2.2. 任务执行流程

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

3. 为什么需要动态线程池

线程池在业务系统应该都有使用到,帮助业务流程提升效率以及管理线程,多数场景应用于大量的异步任务处理。虽然线程池提供了我们许多便利,但也并非尽善尽美,比如下面这些问题就无法很好解决。

  • 线程池随便定义,线程资源过多,造成服务器高负载。
  • 线程池参数不易评估,随着业务的并发提升,业务面临出现故障的风险。
  • 线程池任务堆积,触发拒绝策略,影响既有业务正常运行。

常见的线程池配置:

执行线程池执行任务的类型

  • IO密集型任务:一般来说:文件读写、DB读写、网络请求等
  • CPU密集型任务:一般来说:计算型代码、Bitmap转换、Gson转换等

  • 高并发、任务执行时间短 -->( CPU核数+1 ),减少线程上下文的切换
  • 并发不高、任务执行时间长
    • IO密集型的任务 --> (CPU核数 * 2 + 1)
    • 计算密集型任务 --> ( CPU核数+1 )
  • 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考

但是并没有通用的线程池计算方式。并发任务的执行情况和任务类型相关,IO密集型和CPU密集型的任务运行起来的情况差异非常大,但这种占比是较难合理预估的,这导致很难有一个简单有效的通用公式帮我们直接计算出结果。

既然不能够保证一次计算出来合适的参数,那么是否可以将修改线程池参数的成本降下来,这样至少可以发生故障的时候可以快速调整从而缩短故障恢复的时间呢?基于这个思考,我们是否可以将线程池的参数从代码中迁移到分布式配置中心上,实现线程池参数可动态配置和即时生效,线程池参数动态化前后的参数修改流程对比如下:

4. 动态化线程池

4.1. 整体设计

简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。

为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。

将线程池的配置放置在平台侧,允许简单的查看、修改线程池配置。

4.2. 流程图

5. 基于 Redis 实现

这里主要就是利用 Redis 的发布订阅功能来实现的

  • 在上述流程图中,管理模块可以直接从 Redis 中获取各个线程池的参数,将需要修改的线程池参数推送到 Redis 对应的主题中
  • 动态线程池的 Starter 的订阅者订阅了主题,一有消息就会消费,再将修改好的线程池相关参数上报到 Redis中

5.1. 为什么使用Redis的发布订阅

针对消息订阅发布功能,大部分使用的是kafka、RabbitMQ、ActiveMQ, RocketMQ等这几种,redis的订阅发布功能跟这三者相比,相对轻量,针对数据准确和安全性要求没有那么高可以直接使用

5.2. 具体实现流程

首先在使用线程池的业务端创建相对应的线程池bean

@Slf4j
@EnableAsync
@Configuration
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
public class ThreadPoolConfig {

	/**
	 * 创建线程池
	 */
	@Bean("threadPoolExecutor01")
	public ThreadPoolExecutor threadPoolExecutor01(ThreadPoolConfigProperties properties) {
		// 线程池拒绝策略
		RejectedExecutionHandler handler;
		switch (properties.getPolicy()){
			case "AbortPolicy":
				handler = new ThreadPoolExecutor.AbortPolicy();
				break;
			case "DiscardPolicy":
				handler = new ThreadPoolExecutor.DiscardPolicy();
				break;
			case "DiscardOldestPolicy":
				handler = new ThreadPoolExecutor.DiscardOldestPolicy();
				break;
			case "CallerRunsPolicy":
				handler = new ThreadPoolExecutor.CallerRunsPolicy();
				break;
			default:
				handler = new ThreadPoolExecutor.AbortPolicy();
				break;
		}

		// 创建线程池
		return new ThreadPoolExecutor(properties.getCorePoolSize(),
				properties.getMaxPoolSize(),
				properties.getKeepAliveTime(),
				TimeUnit.SECONDS,
				new LinkedBlockingQueue<>(properties.getBlockQueueSize()),
				Executors.defaultThreadFactory(),
				handler);
	}

	@Bean("threadPoolExecutor02")
	public ThreadPoolExecutor threadPoolExecutor02(ThreadPoolConfigProperties properties) {
		// 线程池拒绝策略
		RejectedExecutionHandler handler;
		switch (properties.getPolicy()){
			case "AbortPolicy":
				handler = new ThreadPoolExecutor.AbortPolicy();
				break;
			case "DiscardPolicy":
				handler = new ThreadPoolExecutor.DiscardPolicy();
				break;
			case "DiscardOldestPolicy":
				handler = new ThreadPoolExecutor.DiscardOldestPolicy();
				break;
			case "CallerRunsPolicy":
				handler = new ThreadPoolExecutor.CallerRunsPolicy();
				break;
			default:
				handler = new ThreadPoolExecutor.AbortPolicy();
				break;
		}

		// 创建线程池
		return new ThreadPoolExecutor(properties.getCorePoolSize(),
				properties.getMaxPoolSize(),
				properties.getKeepAliveTime(),
				TimeUnit.SECONDS,
				new LinkedBlockingQueue<>(properties.getBlockQueueSize()),
				Executors.defaultThreadFactory(),
				handler);
	}
}

然后就是线程池的一些操作(查询线程池列表、根据线程池名称查询线程池配置、更新线程池配置)

其中,由于在业务模块定义了线程池的 Bean,这个 Bean 是 ThreadPoolExecutor 类型的。当 Spring 启动时,它会创建这个Bean,并将其添加到内部的Bean容器中。

其中有个 Map<String, ThreadPoolExecutor> threadPoolExecutorMap集合。这个 Map 是由 Spring 自动注入的,它包含了所有类型为 ThreadPoolExecutor 的 Bean。键是 Bean 的名称,值是对应的 Bean 实例。因此,这个 Map 中会包含业务模块中定义的线程池。

@Slf4j
public class DynamicThreadPoolServiceImpl implements IDynamicThreadPoolService {

	/**
	 * 服务名称
	 */
	private final String applicationName;

	/**
	 * 线程池集合
	 */
	private final Map<String, ThreadPoolExecutor> threadPoolExecutorMap;

	public DynamicThreadPoolServiceImpl(String applicationName, Map<String, ThreadPoolExecutor> threadPoolExecutorMap) {
		this.applicationName = applicationName;
		this.threadPoolExecutorMap = threadPoolExecutorMap;
	}

	/**
	 * 查询线程池列表
	 */
	@Override
	public List<ThreadPoolConfigEntity> queryThreadPoolList() {
		Set<String> threadPoolBeanNames = threadPoolExecutorMap.keySet();
		List<ThreadPoolConfigEntity> threadPoolList = new ArrayList<>(threadPoolBeanNames.size());
		for (String beanName : threadPoolBeanNames) {
			ThreadPoolConfigEntity threadPoolConfigVO = getThreadPoolConfig(beanName);
			threadPoolList.add(threadPoolConfigVO);
		}
		return threadPoolList;
	}

	/**
	 * 根据线程池名称查询线程池配置
	 */
	@Override
	public ThreadPoolConfigEntity queryThreadPoolConfigByName(String threadPoolName) {
		return getThreadPoolConfig(threadPoolName);
	}

	/**
	 * 更新线程池配置
	 */
	@Override
	public void updateThreadPoolConfig(ThreadPoolConfigEntity threadPoolConfigEntity) {
		if (threadPoolConfigEntity == null || !applicationName.equals(threadPoolConfigEntity.getAppName())) return;
		ThreadPoolExecutor threadPoolExecutor = threadPoolExecutorMap.get(threadPoolConfigEntity.getThreadPoolName());
		if (threadPoolExecutor == null) {
			return;
		}

		// 设置参数 「调整核心线程数和最大线程数」
		threadPoolExecutor.setCorePoolSize(threadPoolConfigEntity.getCorePoolSize());
		threadPoolExecutor.setMaximumPoolSize(threadPoolConfigEntity.getMaximumPoolSize());
	}

	/**
	 * 获取线程池配置
	 */
	private ThreadPoolConfigEntity getThreadPoolConfig(String beanName) {
		ThreadPoolExecutor threadPoolExecutor = threadPoolExecutorMap.get(beanName);
		if (threadPoolExecutor == null) {
			return new ThreadPoolConfigEntity(applicationName, beanName);
		}

		ThreadPoolConfigEntity threadPoolConfigVO = new ThreadPoolConfigEntity(applicationName, beanName);
		threadPoolConfigVO.setCorePoolSize(threadPoolExecutor.getCorePoolSize());
		threadPoolConfigVO.setMaximumPoolSize(threadPoolExecutor.getMaximumPoolSize());
		threadPoolConfigVO.setActiveCount(threadPoolExecutor.getActiveCount());
		threadPoolConfigVO.setPoolSize(threadPoolExecutor.getPoolSize());
		threadPoolConfigVO.setQueueType(threadPoolExecutor.getQueue().getClass().getSimpleName());
		threadPoolConfigVO.setQueueSize(threadPoolExecutor.getQueue().size());
		threadPoolConfigVO.setRemainingCapacity(threadPoolExecutor.getQueue().remainingCapacity());

		return threadPoolConfigVO;
	}
}

这些线程池的操作其实都是 Listener 来操作的

@Slf4j
public class RedisAdjustListener implements MessageListener<ThreadPoolConfigEntity> {

    private final IDynamicThreadPoolService dynamicThreadPoolService;

    private final IRegistry registry;

    public RedisAdjustListener(IDynamicThreadPoolService dynamicThreadPoolService, IRegistry registry) {
        this.dynamicThreadPoolService = dynamicThreadPoolService;
        this.registry = registry;
    }

    @Override
    public void onMessage(CharSequence charSequence, ThreadPoolConfigEntity threadPoolConfigEntity) {
        log.info("动态线程池,调整线程池配置。线程池名称:{} 核心线程数:{} 最大线程数:{}", threadPoolConfigEntity.getThreadPoolName(), threadPoolConfigEntity.getPoolSize(), threadPoolConfigEntity.getMaximumPoolSize());
        dynamicThreadPoolService.updateThreadPoolConfig(threadPoolConfigEntity);

        // 更新后上报最新数据
        List<ThreadPoolConfigEntity> threadPoolConfigEntities = dynamicThreadPoolService.queryThreadPoolList();
        registry.reportThreadPool(threadPoolConfigEntities);

        ThreadPoolConfigEntity threadPoolConfigEntityCurrent = dynamicThreadPoolService.queryThreadPoolConfigByName(threadPoolConfigEntity.getThreadPoolName());
        registry.reportThreadPoolConfigParameter(threadPoolConfigEntityCurrent);
        log.info("动态线程池,上报线程池配置:{}", JSON.toJSONString(threadPoolConfigEntity));
    }
}

然后将最新的数据放到注册中心去

public class RedisRegistry implements IRegistry {

    private final RedissonClient redissonClient;

    public RedisRegistry(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 线程池配置列表
     */
    @Override
    public void reportThreadPool(List<ThreadPoolConfigEntity> threadPoolEntities) {
        RList<ThreadPoolConfigEntity> redisList = redissonClient.getList(RegistryEnumVO.THREAD_POOL_CONFIG_LIST_KEY.getKey());
        redisList.clear();
        redisList.addAll(threadPoolEntities);
    }

    /**
     * 线程池配置参数
     */
    @Override
    public void reportThreadPoolConfigParameter(ThreadPoolConfigEntity threadPoolConfigEntity) {
        String cacheKey = RegistryEnumVO.THREAD_POOL_CONFIG_PARAMETER_LIST_KEY.getKey() + ":" + threadPoolConfigEntity.getAppName() + ":" + threadPoolConfigEntity.getThreadPoolName();
        RBucket<ThreadPoolConfigEntity> bucket = redissonClient.getBucket(cacheKey);
        bucket.set(threadPoolConfigEntity, Duration.ofDays(30));
    }
}

因为其实管理端读取到的线程池数据都是从Redis中获取到的,所以也需要有一个定时任务更新注册中心的数据

@Slf4j
public class ThreadPoolDataReportJob {


	private final IDynamicThreadPoolService dynamicThreadPoolService;

	private final IRegistry registry;

	public ThreadPoolDataReportJob(IDynamicThreadPoolService dynamicThreadPoolService, IRegistry registry) {
		this.dynamicThreadPoolService = dynamicThreadPoolService;
		this.registry = registry;
	}

	/**
	 * 每 10 秒上报一次线程池信息
	 */
	@Scheduled(cron = "0/10 * * * * ?")
	public void execReportThreadPoolList() {
		List<ThreadPoolConfigEntity> threadPoolConfigEntities = dynamicThreadPoolService.queryThreadPoolList();
		registry.reportThreadPool(threadPoolConfigEntities);
		log.info("动态线程池,上报线程池信息:{}", JSON.toJSONString(threadPoolConfigEntities));

		for (ThreadPoolConfigEntity threadPoolConfigEntity : threadPoolConfigEntities) {
			registry.reportThreadPoolConfigParameter(threadPoolConfigEntity);
			log.info("动态线程池,上报线程池配置:{}", JSON.toJSONString(threadPoolConfigEntity));
		}

	}

}

5.3. 测试

这些写一个方法模拟线程执行

public ApplicationRunner applicationRunner(ExecutorService threadPoolExecutor01) throws InterruptedException {
	return new ApplicationRunner() {
		@Override
		public void run(ApplicationArguments args) throws Exception {
			while (true) {
				Random random = new Random();
				int randomInitialDelay = random.nextInt(3) + 1;
				int randomSleepTime = random.nextInt(3) + 1;
				threadPoolExecutor01.submit(new Runnable() {
					@Override
					public void run() {
						try {
							TimeUnit.SECONDS.sleep(randomInitialDelay);
							log.info("Task started after " + randomInitialDelay + " seconds.");
							TimeUnit.SECONDS.sleep(randomSleepTime);
							log.info("Task executed for " + randomSleepTime + " seconds.");
						} catch (Exception ex) {
							Thread.currentThread().interrupt();
						}
					}
				});
				Thread.sleep(random.nextInt(10) + 1);
			}
		}
	};
}

修改前

修改最大线程数

修改后

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

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

相关文章

【第22章】Vue实战篇之文章分类

文章目录 前言一、文章分类列表查询1. 界面2. 脚本3. 展示 二、文章分类添加1. 界面2. 接口脚本3. 点击事件 三、文章分类编辑1. 界面2. 接口脚本3. 点击事件 四、文章分类删除1. 界面2. 接口脚本3. 点击事件 总结 前言 这里来学习文章分类相关界面和接口的调用(增删改查)。 一…

新版二开微信发卡小程序源码卡密系统/支持流量主

新版二开微信发卡小程序源码卡密系统支持流量主。裂变扩展多种领取模式二次开发的发卡小程序源码&#xff0c;其后台采用PHP编写&#xff0c;支持用户通过付费购卡或者观看视频广告领取卡密。 该小程序还支持流量主&#xff0c;因为功能需要&#xff0c;就进行了二开&#xff…

Java基础 - 练习(四)打印九九乘法表

Java基础练习 打印九九乘法表&#xff0c;先上代码&#xff1a; public static void multiplicationTable() {for (int i 1; i < 9; i) {for (int j 1; j < i; j) {// \t 跳到下一个TAB位置System.out.print(j "" i "" i * j "\t"…

C语言的网络编程

目录 引言 一、TCP/IP概述 1. TCP&#xff08;Transmission Control Protocol&#xff09; 2. UDP&#xff08;User Datagram Protocol&#xff09; 二、Socket编程基础 1. 服务器端 2. 客户端 三、URL与HTTP编程 1. 使用libcurl进行HTTP请求 表格总结 TCP/IP与Socke…

【Unity | Editor强化工具】项目备忘录工具

经常会被美术和策划同事反复询问某几个问题&#xff0c;每次都要翻Wiki链接给他们&#xff0c;非常折磨人&#xff0c;所以做了个可以在Unity内部显示备忘录的小工具&#xff0c;能够减少一些查找成本&#xff08;另外我觉得&#xff0c;让他们养成查看Unity内触手可及的信息的…

报错:ZeroDivisionError_ division by zero

问题&#xff1a;除数为0 原代码错误来源 # 归一化 , 保留6位小数 w round(w / img_w, 6) h round(h / img_h, 6) cx round(cx / img_w, 6) cy round(cy / img_h, 6) # print(cls_id, cx, cy, w, h) # 结果保存到数据labels文件夹中的txt文件 out_file.write(str(cls_id) …

Redis 主从复制+哨兵+集群

1、总结写在前面 Redis 集群 数据分片 高可用性 Redis 哨兵 主从复制 故障转移 2、主从复制 2.1、准备配置 查看docker 容器 ip docker inspect 容器id | grep IPAddressdocker inspect -f{{.Name}} {{.NetworkSettings.IPAddress}} $(docker ps -aq)修改配置文件 初始…

从零开始搭建创业公司全新技术栈解决方案

从零开始搭建创业公司全新技术栈解决方案 关于猫头虎 大家好&#xff0c;我是猫头虎&#xff0c;别名猫头虎博主&#xff0c;擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程、bug解决思路、开发工具教程、前沿科技资讯、产品评测图文、产品使用体…

Zookeeper 一、Zookeeper简介

1.分布式系统定义及面临的问题 分布式系统是同时跨越多给物理主机&#xff0c;独立运行的多个软件所组成的系统。类比一下&#xff0c;分布式系统就是一群人一起干活。人多力量大&#xff0c;每个服务器的算力是有限的&#xff0c;但是通过分布式系统&#xff0c;由n个服务器组…

Flink 流批一体场景应用及落地情况

摘要&#xff1a;本文由阿里云 Flink 团队苏轩楠老师撰写&#xff0c;旨在介绍 Flink 流批一体在几个常见场景下的应用。内容主要分为以下四个部分&#xff1a; 主要场景 落地情况 未来展望 总结 上篇&#xff1a;流批一体技术简介 在上篇文章中&#xff0c;给大家整体介绍…

有关计算素数的算法

归纳编程学习的感悟, 记录奋斗路上的点滴, 希望能帮到一样刻苦的你! 如有不足欢迎指正! 共同学习交流! 🌎欢迎各位→点赞 👍+ 收藏⭐ + 留言​📝黑暗的笼罩更会凸显光明的可贵! 一、引言 什么是素数 素数,也被称为质数,是指在大于1的自然数中,只能被1和它本身…

【ai】tx2-nx:安装深度学习环境及4.6对应pytorch

参考:https://www.waveshare.net/wiki/Jetson_TX2_NX#AI.E5.85.A5.E9.97.A8 英伟达2021年发布的的tritionserver 2.17 版本中,backend 有tensorflow1 和 onnxruntime ,他们都是做什么用的,作为backend 对于 triton 推理server意义是什么,是否应该有pytorch? Triton Infer…

小程序中用font-spider压缩字体后,字体没效果(解决办法)

因为项目中需要引入外部字体&#xff0c;有两种方案&#xff0c; 第一是把字体下载到本地&#xff0c; 第二种是cdn请求服务器放字体的地址 但是小程序是有大小限制的&#xff0c;所以必须要压缩字体大小&#xff0c;这时候有些人就说了&#xff0c;那把字体放在服务器上&a…

从复用性角度阐述中台建设

目录 复用性中台定义深思中台建设产品线形态何时演变中台能力落地中台 业务中台架构总结 技术学习永不止步&#xff0c;最近也是看了很多关于架构设计相关的专栏&#xff0c;慢慢总结出来一部分知识&#xff0c;代入自己的思考与理解&#xff0c;以及结合并反思自己之前公司的架…

windows和linux下清空Redis

前言 在本文中&#xff0c;我们将详尽阐述在Windows与Linux操作系统中有效清除Redis缓存的实践方法&#xff0c;旨在为您提供清晰、高效的指导流程&#xff0c;确保数据管理的灵活性与效率。 windows下推荐两款可视化工具 Another Redis Desktop Manager 这是我用的最多也是最…

安卓手机删除的照片如何恢复?2个有效方法,教你找回

手机相册就像是我们的私人宝藏&#xff0c;里面装满了无数珍贵的回忆。但是&#xff0c;如果你不小心把里面的宝贝照片给删了&#xff0c;那可真是让人欲哭无泪啊&#xff01;删除的照片如何恢复&#xff1f;今天&#xff0c;我要给你介绍几个方法&#xff0c;让你轻松找回那些…

网络编程(二)TCP编程 TCP粘包问题

文章目录 一、TCP网络编程&#xff08;一&#xff09;流程&#xff08;二&#xff09;相关函数1. socket2. bind3. listen4. accept5. connect 二、收发函数&#xff08;一&#xff09;send函数&#xff08;二&#xff09;recv函数 三、TCP粘包问题&#xff08;一&#xff09;将…

linux精通 4.1

2.1.3 http服务器实现 目的 reactor应用——webserver webclient 每次上课前 看大纲down code 复习&#xff1a; 不行啊 编译给的代码报错啊 给的最新的不是0430那一版就不行啊 reactor.c:(.text0x254): relocation truncated to fit: R_X86_64_PC32 against symbol begin de…

Gobject tutorial 八

The GObject base class Object memory management Gobject的内存管理相关的API很复杂&#xff0c;但其目标是提供一个基于引用计数的灵活的内存管理模式。 下面我们来介绍一下&#xff0c;与管理引用计数相关的函数。 Reference Count 函数g_object_ref和g_object_unref的…

车载测试面试项目看这一套就够了!车载测试___自我讲解项目

面试官您好&#xff0c;我叫xx来自安微&#xff0c;今年xx岁&#xff0c;毕业于安微新华学院&#xff0c;我是从2017年开始接触软件测试行业&#xff0c;目前从事软件测试工作有5年多时间&#xff0c;第一家公司做了电商和进销存项目app和web都有做过&#xff0c;上家公司做了车…