【系统编程】线程池以及API接口简介

news2024/11/19 0:33:31
  • (꒪ꇴ꒪ ),Hello我是祐言QAQ
  • 我的博客主页:C/C++语言,数据结构,Linux基础,ARM开发板,网络编程等领域UP🌍
  • 快上🚘,一起学习,让我们成为一个强大的攻城狮!
  • 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
  • 作者水平很有限,如果发现错误,请在评论区指正,感谢🙏


        线程池(Thread Pool)是一种并发编程的设计模式,它用于管理和复用线程,以便更有效地处理并发任务。线程池的主要目标是降低线程的创建和销毁成本,提高系统的性能和资源利用率

一、API接口

        API(Application Programming Interface)接口是一组定义了软件组件之间如何互相通信和交互的规范和协议。API允许不同的软件模块、应用程序或系统之间共享功能和数据,从而实现各种复杂的任务和功能。

        我们在学习线程池之前要明白,线程池本身不是一个API接口,而是一种用于管理和执行任务的并发编程模型。然而,线程池通常会被包装在API接口中,以便其他开发人员可以更容易地使用它来执行并发任务。

        API接口通常定义了一组可用于与软件组件、服务或库进行交互的方法和函数。在这种情况下,如果创建了一个包含线程池的库,并提供了一组方法或函数来操作和管理线程池,那么这些方法或函数可以被视为API接口的一部分。

        例如,可以设计一个具有以下功能的线程池API接口:

init_pool():初始化线程池
add_task():向线程池添加任务
add_thread():添加新的工作线程
remove_thread():从线程池中删除工作线程
destroy_pool():销毁线程池

      
        这些方法将构成线程池API接口的一部分,其他开发人员可以使用这些方法来实现并发任务执行,而无需了解线程池的内部工作原理。

        所以,线程池可以成为一个可用于构建API接口的组件,以简化并发编程任务的处理,这就是我们了解AIP接口的目的,当然知道这些还不足以写出一个线程池应用程序,我们还需先了解一下有关线程池的结构体。

二、线程池相关的结构体

        线程池相关的结构体在线程池的设计中扮演着重要角色,用于管理线程池的状态、任务队列等信息。

1.线程池结构体(Thread Pool)

        线程池结构体通常包含了线程池的各种属性和信息,用于管理线程池的整体状态。以下是一般线程池结构体的示例:

typedef struct ThreadPool {
    pthread_mutex_t lock;           // 互斥锁,保护线程池内部数据
    pthread_cond_t cond;            // 条件变量,用于线程之间的同步
    bool shutdown;                  // 线程池销毁标志
    struct Task *task_list;         // 任务链队列
    pthread_t *tids;                // 存储线程ID的数组
    unsigned int waiting_tasks;     // 等待执行的任务数量
    unsigned int active_threads;    // 活跃线程数量
} ThreadPool;
  • lockcond是用于线程同步的互斥锁和条件变量;
  • shutdown标志用于指示线程池是否正在销毁;
  • task_list是一个任务链队列,存储待执行的任务;
  • tids是存储线程ID的数组;
  • waiting_tasks记录等待执行的任务数量;
  • active_threads表示当前活跃线程数量。

2.任务结构体(Task)

        任务结构体表示线程池中的任务,包含了任务函数指针和参数。以下是一个示例:

//任务结构体
struct task
{
	void *(*task)(void *arg);	//返回值为void *的函数指针,参数列表void *arg,表示任务的地址
	void *arg;					//表示任务需要的参数
	struct task *next;			//表示下一个任务的地址
};
  • task 是指向任务函数的指针,任务函数接受一个void *参数;
  • arg 是传递给任务函数的参数;
  • next 是一个指向下一个任务的指针,用于构建任务队列。


        这些结构体协同工作,帮助线程池管理任务的执行和线程的管理。线程池结构体用于维护线程池的状态,任务结构体用于表示具体的任务。线程池中的线程会不断从任务队列中取出任务并执行,同时线程池负责管理线程的生命周期。

三、线程池

        线程池(Thread Pool)是一种并发编程的设计模式,它用于管理和复用线程,以便更有效地处理并发任务。线程池的主要目标是降低线程的创建和销毁成本,提高系统的性能和资源利用率

1.基本原理


        (1)线程复用: 线程池在启动时创建一组线程,这些线程一直保持活动状态,可用于处理任务。线程复用消除了频繁创建和销毁线程的开销,这也是为什么我们要学习线程池的原因。

        (2)任务队列: 线程池通常包括一个任务队列,用于存储等待执行的任务(条件变量)。当任务到达时,线程池将任务放入队列,并从池中的空闲线程中选择一个来执行任务。

        (3)线程调度: 线程池负责调度任务并分配给空闲线程。一旦任务完成,线程将返回池中等待下一个任务。

        (4)线程池大小控制: 线程池的大小通常是有限的,可以根据系统资源和性能需求进行配置。这有助于避免创建过多线程,从而导致资源耗尽和性能下降,也就是做到合理利用资源。

2.组成部分


        (1)线程池管理器(Thread Pool Manager): 负责创建、管理和监控线程池的核心组件。它维护线程池的状态,包括活动线程数、等待任务数等。

        (2)任务队列(Task Queue): 用于存储等待执行的任务。任务可以是函数、方法或对象,线程池从队列中取出任务并将其分配给可用线程。

        (3)工作线程(Worker Threads): 线程池中的线程,用于执行任务。这些线程在初始化时启动,并在完成任务后返回池中以供重用。

        (4)任务接口(Task Interface): 描述任务的接口或抽象类,通常包括一个run方法或函数,线程池根据此接口来执行任务。

四、C语言实现线程池

1. 初始化线程池

          用于初始化线程池,设置线程池的初始状态

原型:

        bool init_pool(thread_pool * pool, unsigned int threads_number);

参数:
        pool        线程池结构体指针,用于表示要初始化的线程池
        threads_number        指定线程池中的初始线程数量

// 初始化线程池结构体里面的成员,根据传入的线程个数,创建线程
bool init_pool(thread_pool *pool, unsigned int threads_number)
{
	//初始化互斥锁
	pthread_mutex_init(&pool->lock, NULL);
	//初始化条件变量
	pthread_cond_init(&pool->cond, NULL);

	// 关闭销毁线程池标识
	pool->shutdown = false;
	// 任务队列头结点
	pool->task_list = malloc(sizeof(struct task)); 
	// 线程ID的指针申请空间
	pool->tids = malloc(sizeof(pthread_t) * MAX_ACTIVE_THREADS);
	// 判断任务队列头结点指针跟线程ID的指针是否申请成功
	if(pool->task_list == NULL || pool->tids == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	//将任务链式队列的下一个节点的地址初始化
	pool->task_list->next = NULL;
	//初始化任务个数0个
	pool->waiting_tasks = 0;
	//初始化活跃线程个数为传入的threads_number个
	pool->active_threads = threads_number;

	int i;
	// 循环创建指定数目线程
	for(i = 0; i<pool->active_threads; i++) 
	{
		//调用pthread_create函数创建线程,线程ID存放在pool->tids的数组里面
		if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0)
		{
			perror("create threads error");
			return false;
		}
	}

	return true;
}

2. 添加任务

        将一个任务添加到线程池的任务队列中,等待线程池的线程执行。

原型:

        bool add_task(thread_pool *pool, void *(*do_task)(void *arg), void * arg);

参数:
        pool        线程池结构体指针,表示要添加任务的线程池
        do_task        任务函数的指针,表示要执行的任务
        arg        任务函数需要的参数,可以是任何类型的数据

// 往链式任务队列添加任务,单个唤醒线程去执行任务
bool add_task(thread_pool *pool, void *(*task)(void *arg), void *arg)
{
	// 新任务节点
	struct task *new_task = malloc(sizeof(struct task));
	//新任务节点创建失败
	if(new_task == NULL)
	{
		perror("allocate memory error");
		return false;
	}
	//新任务节点的函数指针做初始化
	new_task->task = task;
	//新任务节点的函数指针需要的参数做初始化
	new_task->arg = arg;
	//新任务节点的下一个节点的地址初始化为NULL
	new_task->next = NULL;

	// 访问任务队列前获取互斥锁,此处无需注册取消处理例程
	pthread_mutex_lock(&pool->lock);

	//如果 任务链队列里面任务个数 大于等于 最大任务个数
	if(pool->waiting_tasks >= MAX_WAITING_TASKS)
	{
		//解锁
		pthread_mutex_unlock(&pool->lock);

		fprintf(stderr, "too many tasks.\n");
		free(new_task);

		return false;
	}
	//拿链式队列的头节点指针
	struct task *tmp = pool->task_list;
	//循环遍历找到最后一个节点的地址
	while(tmp->next != NULL)
	{
		tmp = tmp->next;
	}
	// 添加新的任务节点
	tmp->next = new_task;
	// 等待任务个数+1
	pool->waiting_tasks++;

	// 释放互斥锁
	pthread_mutex_unlock(&pool->lock);
	// 并唤醒其中一个阻塞在条件变量上的线程
	pthread_cond_signal(&pool->cond);

	return true;
}

3. 添加活跃线程

        向线程池中添加额外的活跃线程,增加线程池的处理能力。

原型:

        int add_thread(thread_pool *pool, unsigned int additional_threads);

参数:
        pool        线程池结构体指针,表示要添加线程的线程池        
        additional_threads        指定要添加的额外线程数量        

// 根据传入的数量创建线程
int add_thread(thread_pool *pool, unsigned additional_threads)
{
	//添加活跃线程个数为0,不需要往后执行了
	if(additional_threads == 0)
	{
		return 0;
	}
	// 定义一个变量total_threads = 当前活跃线程个数 + 添加活跃线程个数
	unsigned total_threads = pool->active_threads + additional_threads;
                           
	int i, actual_increment = 0;
	// 循环地创建若干指定数目的线程
	for(i = pool->active_threads; i < total_threads && i < MAX_ACTIVE_THREADS; i++)
	{
		if(pthread_create(&((pool->tids)[i]), NULL, routine, (void *)pool) != 0)
		{
			perror("add threads error");
			//添加活跃线程个数如果一开始就为0,表示一个都没有创建成功
			if(actual_increment == 0)
			{
				return -1;
			}
			break;
		}
		actual_increment++;
	}
	//更新活跃线程个数,用创建成功的个数加上之前的个数
	pool->active_threads += actual_increment;

	return actual_increment;
}

4. 删除活跃线程

        从线程池中删除指定数量的活跃线程,减少线程池的处理能力。

原型:

        int remove_thread(thread_pool *pool, unsigned int removing_threads);

参数:
        pool        线程池结构体指针,表示要删除线程的线程池        
        removing_threads        指定要删除的线程数量        

// 根据传入的个数去删除,返回删除之后的剩下的活跃线程个数
int remove_thread(thread_pool *pool, unsigned int removing_threads)
{
	//如果删除的线程个数为0,直接返回当前的活跃线程个数
	if(removing_threads == 0)
	{
		return pool->active_threads;
	}
	//定义一个变量删除之后剩下的线程个数 = 当前活跃线程个数-删除活跃线程个数
	int remain_threads = pool->active_threads - removing_threads;
	//如果删除之后的线程个数小于0,保留一个,如果不小于0,就有多少保留多少

	remain_threads = remain_threads > 0 ? remain_threads : 1;

	int i; 
	// 循环地取消掉指定数目的线程
	for(i = pool->active_threads-1; i>remain_threads-1; i--)
	{
		errno = pthread_cancel(pool->tids[i]);
		if(errno != 0)
		{
			break;
		}
	}
	//如果一个都没有删,返回-1
	if(i == pool->active_threads-1)
	{
		return -1;
	}
	else
	{
		//更新最大活跃线程个数
		pool->active_threads = i+1;
		return i+1;
	}
}

5. 销毁线程池

        销毁线程池,释放线程池占用的资源,并停止线程池的运行。

原型:

        bool destroy_pool(thread_pool *pool);

参数:

  pool        线程池结构体指针,表示要销毁的线程池

// 释放资源
bool destroy_pool(thread_pool *pool)
{
	//线程池销毁标志为真
	pool->shutdown = true;
	//广播唤醒条件变量等待队列里面的线程
	pthread_cond_broadcast(&pool->cond);

	int i;
	for(i=0; i<pool->active_threads; i++)
	{
		//pthread_join( )指定的线程如果尚在运行,那么他将会阻塞等待
		errno = pthread_join(pool->tids[i], NULL);
		if(errno != 0)
		{
			printf("join tids[%d] error: %s\n", i, strerror(errno));
		}
		else
		{
			printf("[%u] is joined\n", (unsigned)pool->tids[i]);
		}	
	}

	free(pool->task_list);
	free(pool->tids);
	free(pool);

	return true;
}

6.线程任务函数

        routine( )函数是线程池中线程的主要工作函数,它执行以下任务:

        ①不断地从任务队列中取出任务;
        ②执行取出的任务;
        ③如果任务队列为空且线程池没有被销毁,则进入等待状态,等待新任务的到来。

//创建线程之后,开始去执行任务,有任务就执行,没有任务,线程进入条件变量等待队列等待唤醒执行任务
void *routine(void *arg)
{
	//接收传入进来的参数pool
	thread_pool *pool = (thread_pool *)arg;
	//定义任务结构体指针变量
	struct task *p;

	while(1)
	{
		// 访问任务队列前加锁,为防止取消后死锁,注册处理例程 handler
		pthread_cleanup_push(handler, (void *)&pool->lock);
		//加锁访问任务队列
		pthread_mutex_lock(&pool->lock);

		// 若当前没有任务,且线程池关闭标志未关闭,则进入条件变量等待队列睡眠,等待新任务的到来或线程池关闭
		while(pool->waiting_tasks == 0 && !pool->shutdown)
		{
			//解锁 进入条件变量等待队列睡眠   收到通知的时候,加锁
			pthread_cond_wait(&pool->cond, &pool->lock);
		}

		// 若当前没有任务,且线程池关闭标识为真,则立即释放互斥锁并退出
		if(pool->waiting_tasks == 0 && pool->shutdown == true)
		{
			//解锁
			pthread_mutex_unlock(&pool->lock);
			//线程退出
			pthread_exit(NULL);
		}

		// 若当前有任务,则消费任务队列中的任务
		// 拿链式队列的下一个节点的地址
		p = pool->task_list->next;
		//将链式队列当前指向,指向下一个
		pool->task_list->next = p->next;
		//任务个数-1
		pool->waiting_tasks--;

		// 释放互斥锁,并弹栈 handler(但不执行他)
		pthread_mutex_unlock(&pool->lock);
		pthread_cleanup_pop(0);

		// 执行任务,并且在此期间禁止响应取消请求,执行期间用pthread_cancel发送取消请求
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
		//调用任务结构体里面的函数,传参数
		(p->task)(p->arg);	// task(arg)
		//执行任务完成,并且在此期间使能响应取消请求
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
		//释放p指向的地址空间
		free(p);
	}
	//线程退出
	pthread_exit(NULL);
}

7.任务函数

void *mytask(void *arg);

        该函数接受一个 void 指针类型的参数 arg,并返回一个 void 指针。函数主要用于你想要完成的任务操作,这里举例了一个文件复制函数:

//任务函数(想要完成的操作)
void *mytask(void *arg)
{
	//文件复制
	int fd1 = open("./1.txt", O_RDWR);//int fd1 = open(arg->first, O_RDWR);
	if (fd1 == -1)
	{
		perror("open fd1 error");
		return NULL;
	}
	int fd2 = open("./2.txt", O_RDWR|O_CREAT, 0777);//int fd2 = open(arg->second, O_RDWR|O_CREAT, 0777);
	if (fd2 == -1)
	{
		perror("open fd2 error");
		return NULL;
	}
	char buf[1024];
	int size;
	while(1)
	{
		bzero(buf, 1024);
		size = read(fd1, buf, 1024);
		if (size==0)
		{
			break;
		}
		write(fd2, buf, size);
	}
	close(fd1);
	close(fd2);
	return NULL;
}

        这里我们创建了五条线程的一个线程池,来复制一个拥有50个文件大小为45MB的大文件,可以看到用时4.433s,然而如果采用单线程处理的话,至少时间在8s左右。当然具体情况因个人电脑配置不同而异。

五、总结

        让我们用一个类比来解释一下线程池,每辆出租车就是一个线程它的工作是执行各种任务,就像司机可以运送乘客到不同的目的地一样。而线程池就像是一家出租公司这家公司拥有多组出租车(线程),并根据需要分配任务给这些出租车。公司负责管理、维护和监控这些出租车,以确保它们随时可以为乘客提供服务。任务(工作)就像是乘客需要到达的目的地。这可以是任何需要执行的工作,例如计算、数据处理、文件上传等等。线程池接受这些任务,并将它们分配给可用的出租车(线程),从而高效的完成他们。

1.优点

  • 资源控制: 线程池可以限制同时运行的线程数量,有效控制系统资源的使用。
  • 性能提升: 通过减少线程的创建和销毁,线程池可以提高应用程序的性能,减少了线程切换的开销。
  • 任务管理: 线程池可以管理任务的排队和执行,确保任务按照顺序或优先级执行。
  • 可伸缩性: 线程池可以根据系统负载动态调整线程数量,以适应不同的工作负载。

2.适用场景

        线程池适用于需要并发执行多个任务的情况,特别是在以下情况下使用效果更佳:

  • Web服务器处理多个并发请求。
  • 数据库连接池管理多个数据库连接。
  • 后台任务处理,如日志处理、邮件发送、文件处理等。
  • 任何需要并发执行的计算密集型或I/O密集型任务。

3.注意事项

  • 线程池的大小应该根据系统资源和负载需求进行调整,过大的线程池可能会消耗大量内存,而过小的线程池可能会导致任务排队和性能下降。

  • 线程池中的任务应该是独立的,不应该有共享状态或依赖关系,以避免竞态条件和死锁。

  • 异常处理对于线程池非常重要,确保捕获和处理任务中的异常,以避免线程终止和资源泄漏。

        更多C/C++语言Linux系统数据结构ARM板实战相关文章,关注专栏:

   手撕C语言

            玩转linux

                    脚踢数据结构

                            系统、网络编程

                                     探索C++

                                             6818(ARM)开发板实战

📢写在最后

  • 今天的分享就到这啦~
  • 觉得博主写的还不错的烦劳 一键三连喔~
  • 🎉🎉🎉感谢关注🎉🎉🎉

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

非比较排序——计数排序

本章gitee代码&#xff1a;计数排序 文章目录 &#x1f347;0. 前言&#x1f348;1. 思路&#x1f349;2. 代码实现&#x1f34a;3. 优势与缺陷&#x1f34b;4. 其他的非比较排序&#x1faf4;桶排序&#x1faf4;基数排序 &#x1f347;0. 前言 传统的排序方法通常需要逐个比…

嵌入式Linux开发实操(十五):nand flash接口开发(2)

通用NAND驱动程序支持几乎所有基于NAND的芯片,并将它们连接到Linux内核的内存技术设备(MTD)子系统。这个接口走的是nand的并口,可以在shell的/dev中看到设备,比如/mtd0、/mtd0ro…,mtdblock0、mtdblock1… sysfs在设备层次结构中提供了几个视角。设备必须挂在某条总线bus…

Linux相关指令(下)

cat指令 查看目标文件的内容 常用选项&#xff1a; -b 对非空输出行编号 -n 对输出的所有行编号 -s 不输出多行空行 一个重要思想&#xff1a;linux下一切皆文件&#xff0c;如显示器文件&#xff0c;键盘文件 cat默认从键盘中读取数据再打印 退出可以ctrlc 输入重定向<…

CSP 202305-2 矩阵运算

样例输入 3 2 1 2 3 4 5 6 10 10 -20 -20 30 30 6 5 4 3 2 1 4 0 -5 样例输出 480 240 0 0 -2200 -1100 答题 注意数值范围用int已经不行了&#xff0c;必须要用long long 而且矩阵运算涉及到三层循环&#xff0c;可以利用cache机制减少取值时间&#xff0c;先将右矩阵转置…

Unity2D实现左右移动的敌人角色

文章目录 环境与角色创建敌人角色 敌人脚本检测前方是否有地面获取检测点检测地面 完整代码运行结果其他文章 环境与角色 创建敌人角色 简单起见&#xff0c;突出脚本的内容&#xff0c;我们就只创建一个圆形用来当做当前的敌人角色。 为分清左右&#xff0c;我们再为敌人角色…

时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神经网络时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现TCN-BiLSTM时间卷积双向长短期记忆神…

APACHE NIFI 删除组件报错

报错信息&#xff1a; Node 192.168.93.206:8443 is unable to fulfill this request due to: Controller Service 0e733621-f4ad-139e-8a25-8ad317eb6453 cannot be deleted because it is not disabled 步骤1&#xff1a;进入报错的流程里面&#xff0c;我的是到报错的Proce…

Arduino驱动LX1972传感器(光照传感器篇)

LX1972环境光(可见光)传感器,对可见光照度的反应特性与人眼的特性类似,可以模拟人对环境光线的强度的判断,从而方便做出与人友好互动的应用,可应用于照明控制、屏幕背光控制等。 1、传感器特性 传感器技术指标如下: 工作温度: -40~80C照度范围: 1 – 800Lux输出信号:…

【虹科干货】基本元件可靠性测试方案

01 内容摘要 本文介绍了一种基于可变形测试模块的电感和电阻测试系统&#xff0c;旨在解决传统手动测量方法存在的局限性和需求提升的问题。 该系统通过引入可变形测试模块、大规模矩阵和配套设备&#xff0c;实现了电感和电阻的大规模自动化测试。基于可变形测试模块的电感和…

MySQL——连接查询与子查询

一、连接查询 单表查询&#xff1a;在一张表当中查询数据&#xff0c;叫做单表查询。 连接查询&#xff0c;结合俩&#xff08;多&#xff09;张表&#xff0c;在俩张&#xff08;多&#xff09;表当中查询数据&#xff0c;在一张表当中查询一部分&#xff0c;在另一张表当中…

10.(Python数模)(预测模型二)LSTM回归网络(1→1)

LSTM回归网络&#xff08;1→1&#xff09; 长短期记忆网络 - 通常只称为“LSTM” - 是一种特殊的RNN&#xff0c;能够学习长期的规律。 它们是由Hochreiter&#xff06;Schmidhuber&#xff08;1997&#xff09;首先提出的&#xff0c;并且在后来的工作中被许多人精炼和推广。…

31 WEB漏洞-文件操作之文件包含漏洞全解

目录 文件包含漏洞原理检测类型利用修复 本地包含-无限制&#xff0c;有限制远程包含-无限制&#xff0c;有限制各种协议流玩法文章介绍读取文件源码用法执行php代码用法写入一句话木马用法每个脚本支持的协议玩法 演示案例某CMS程序文件包含利用-黑盒CTF-南邮大&#xff0c;i春…

ChatGPT 制作转化率分析漏斗图的制作

像这样的转换率漏斗图使用前端可视化技术就可以完成。 使用ChatGPT OpenAI来完成代码的编写。 我们将完整的代码给大家复制到下面: <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>ECharts</title><!-- 引入…

如何在IPhone 14、14 Pro和14 Pro Max上添加屏幕锁定

当你第一次获得iPhone时&#xff0c;系统会提示你为它创建一个密码&#xff0c;这样只有你才能访问它。你应该使用一个必须输入的密码&#xff0c;以便在iPhone 14被唤醒或打开时解锁它。这将提供更高级别的保护。当你打开数据保护时&#xff0c;iPhone上的数据会被加密&#x…

恒运资本:沪指涨逾1%,金融、地产等板块走强,北向资金净买入超60亿元

4日早盘&#xff0c;两市股指盘中强势上扬&#xff0c;沪指、深成指涨超1%&#xff0c;上证50指数涨近2%&#xff1b;两市半日成交约5500亿元&#xff0c;北向资金大举流入&#xff0c;半日净买入超60亿元。 截至午间收盘&#xff0c;沪指涨1.12%报3168.38点&#xff0c;深成指…

Vue——vue3中的ref和reactive数据理解以及父子组件之间props传递的数据

ref()函数 这是一个用来接受一个内部值&#xff0c;返回一个响应式的、可更改的 ref 对象&#xff0c;此对象只有一个指向其内部值的属性 .value。 作用&#xff1a;创建一个响应式变量&#xff0c;使得某个变量在发生改变时可以同步发生在页面上。 模板语句中使用这个变量时…

解决外接显示器后Edge浏览器地址栏等变得很大的问题

解决外接显示器后Edge浏览器地址栏等变得很大的问题 edge设置里外观——触控模式&#xff0c;把触控模式关了

uni-app 之 v-on:click点击事件

uni-app 之 v-on:click点击事件 image.png <template><!-- vue2的<template>里必须要有一个盒子&#xff0c;不能有两个&#xff0c;这里的盒子就是 view--><view>--- v-on:click点击事件 ---<view v-on:click"onclick">{{title}}<…

浅谈Mysql读写分离的坑以及应对的方案 | 京东云技术团队

一、主从架构 为什么我们要进行读写分离&#xff1f;个人觉得还是业务发展到一定的规模&#xff0c;驱动技术架构的改革&#xff0c;读写分离可以减轻单台服务器的压力&#xff0c;将读请求和写请求分流到不同的服务器&#xff0c;分摊单台服务的负载&#xff0c;提高可用性&a…

华为OD机试 - 等和子数组最小和 - 深度优先搜索(Java 2022 Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》…