linux 线程池 (C语言实现)

2023-05-16

线程池分为三个部分:

  1. 任务队列
  2. 工作线程,N个(任务队列的消费者)
  3. 管理者线程,1个

主要实现的函数:

  1. 创建线程池
  2. 线程池添加任务
  3. 销毁线程池
  4. 任务函数(做什么)
  5. 工作线程函数
  6. 管理者线程函数

线程池结构体:

typedef struct ThreadPool
{
    Task* taskQ;    //任务队列
    int queueCapacity;  //容量
    int queueSize;      //当前任务个数
    int queueFront;     //队头 -> 取数据
    int queueRear;      //队尾 -> 放数据

    pthread_t managerID;     //管理者线程ID
    pthread_t* threadIDs;    //工作的线程ID 
    int minNum;              //最小线程数
    int maxNum;              //最小线程数
    int busyNum;             //忙的线程个数
    int liveNum;             //存活的线程个数
    int exitNum;             //要销毁的线程个数
    pthread_mutex_t mutexPool;  //互斥锁pthread_mutex_t,锁整个线程池
    pthread_mutex_t mutexBusy;  //锁busyNum变量
    pthread_cond_t notFull;     // pthread _ cond _t表示多线程的条件变量,任务队列是不是满了
    pthread_cond_t notEmpty;    //任务队列是不是空了

    int shutdown;               //是不是要销毁线程池,销毁为1,不销毁为0
};

 创建线程池函数:

ThreadPool* ThreadPoolCreate(int min, int max, int queueSize)
{
	ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
	do
	{
		if (pool == NULL)
		{
			printf("malloc threadpool fail...\n");
			break;
		}
		//开辟工作线程空间
		pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
		if (pool->threadIDs == NULL)
		{
			printf("malloc threadIDs fail...\n");
			break;
		}
		memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
		pool->minNum = min;
		pool->maxNum = max;
		pool->busyNum = 0;
		pool->liveNum = min;
		pool->exitNum = 0;

		//互斥锁、条件变量的初始化
		if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
			pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
			pthread_cond_init(&pool->notFull, NULL) != 0 ||
			pthread_cond_init(&pool->notEmpty, NULL) != 0)
		{
			printf("mutex or cond init fail...\n");
			break;
		}

		//任务队列
		pool->taskQ = (Task*)malloc(sizeof(Task) * queueSize);
		pool->queueCapacity = queueSize;
		pool->queueSize = 0;
		pool->queueFront = 0;
		pool->queueRear = 0;

		pool->shutdown = 0;

		//创建管理者线程
		pthread_create(&pool->managerID, NULL, manager, pool);
		//创建工作线程
		for (int i = 0; i < min; i++)
		{
			pthread_create(&pool->threadIDs[i], NULL, workers, pool);
		}
		return pool;
	} while (0);

	//释放资源
	if (pool && pool->threadIDs)
	{
		free(pool->threadIDs);
	}
	if (pool && pool->taskQ)
	{
		free(pool->taskQ);
	}
	if (pool)
	{
		free(pool);
	}
	return NULL;
}

工作线程函数:

void* workers(void* arg)
{
	ThreadPool* pool = (ThreadPool*)arg;
	while (1)
	{
		pthread_mutex_lock(&pool->mutexPool);

		//判断任务队列是否为空
		while (pool->queueSize == 0 && !pool->shutdown)
		{
			//阻塞线程
			pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);

			//判断是不是要销毁线程
			if (pool->exitNum > 0)
			{
				pool->exitNum--;
				if (pool->liveNum > pool->minNum)
				{
					pool->liveNum--;
					pthread_mutex_unlock(&pool->mutexPool);
					threadExit(pool);
				}

			}
		}

		//判断线程池是否要关闭
		if (pool->shutdown)
		{
			pthread_mutex_unlock(&pool->mutexPool);
			threadExit(pool);
		}

		/*从任务列队中取出一个任务*/
		Task task;
		task.function = pool->taskQ[pool->queueFront].function;
		task.arg = pool->taskQ[pool->queueFront].arg;
		//每取出一个任务后需要移动任务队列的队头:(头指针+1)整除 队列大小 
		pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
		pool->queueSize--;

		/*生产者生产了产品之后唤醒消费者,消费者消耗了产品之后唤醒生产者*/
		//消费了一个任务,需要唤醒生产者线程
		//pthread_cond_signal详解: https://blog.csdn.net/fanyun_01/article/details/106975364
		pthread_cond_signal(&pool->notFull);//消费了任务通知生产者
		pthread_mutex_unlock(&pool->mutexPool);

		
		printf("thread % ld start working...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum++;
		pthread_mutex_unlock(&pool->mutexBusy);
        //开始任务
		task.function(task.arg);
		free(task.arg);
		task.arg = NULL;

		//结束任务
		printf("thread % ld end working...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum--;
		pthread_mutex_unlock(&pool->mutexBusy);
	}
	return NULL;
}

管理者线程函数:

void* manager(void* arg)
{
	ThreadPool* pool = (ThreadPool**)arg;
	while (!pool->shutdown)
	{
		//每3秒检查一次
		sleep(3);

		//取出线程池中当前的任务数和活着的线程数(线程数是共享资源,操作里面的数据需要加锁)
		pthread_mutex_lock(&pool->mutexPool);
		int queueSize = pool->queueSize;
		int liveNum = pool->liveNum;
		pthread_mutex_unlock(&pool->mutexPool);


		//取出忙的线程数
		pthread_mutex_lock(&pool->mutexBusy);
		int busyNum = pool->busyNum;
		pthread_mutex_unlock(&pool->mutexBusy);

		//添加线程:
		//1.定义添加线程时机的规则: 任务个数>存活的线程个数 && 存活的线程个数<最大线程数
		if (queueSize > liveNum && liveNum < pool->maxNum)
		{
			int counter = 0;
			//切记加锁
			pthread_mutex_lock(&pool->mutexPool);
			for (int i = 0; i < pool->maxNum && counter < NUMBER && liveNum < pool->maxNum; ++i)
			{
				if (pool->threadIDs[i] == 0)
				{
					pthread_create(&pool->threadIDs[i], NULL, workers, pool);
					counter++;
					pool->liveNum++;

				}
			}
			pthread_mutex_unlock(&pool->mutexPool);
		}

		//销毁线程
		//1.定义销毁线程时机的规则: 忙的个数*2<存活的线程个数 && 存活的线程个数>最小线程数
		if (busyNum * 2 < liveNum && liveNum > pool->minNum)
		{
			//切记加锁
			pthread_mutex_lock(&pool->mutexPool);
			pool->exitNum = NUMBER;
			pthread_mutex_unlock(&pool->mutexPool);
			for (int i = 0; i < NUMBER; i++)
			{
				//这里只能让工作的线程自杀,不能主动销毁工作线程
				//使阻塞的线程脱离阻塞状态(pool->notEmpty)
				pthread_cond_signal(&pool->notEmpty);
			}
		}
	}
	return NULL;
}

线程池任务添加函数:

void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{
	pthread_mutex_lock(&pool->mutexPool);
	while (pool->queueSize == pool->queueCapacity && !pool->shutdown)
	{
		//阻塞管理者(生产者)线程
		pthread_cond_wait(&pool->notFull, &pool->mutexPool);
	}
	if (pool->shutdown)
	{
		pthread_mutex_unlock(&pool->mutexPool);
		return;
	}

	//添加任务
	pool->taskQ[pool->queueRear].function = func;//
	pool->taskQ[pool->queueRear].arg = arg;
	pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
	pool->queueSize++;
	pthread_cond_signal(&pool->notEmpty);//有新任务通知消费者
	pthread_mutex_unlock(&pool->mutexPool);

}

具体实现:

main.c:

#include <stdio.h>
#include"threadpool.h"
#include<pthread.h>
#include<stdlib.h>
#include <unistd.h>
/*
* <cstdio> (stdio.h)

C库执行输入/输出操作:

输入和输出操作也可以在C++实现,通过使用C标准输入和输出库(cstdio,在C语言中称为stdio.h)。

这个库使用流来操作物理设备如键盘,打印机,终端或者系统支持的任何其他类型的文件。
*/

void taskFunc(void* arg)
{
    int num = *(int*)arg;
    printf("thread %ld is working ,number=%d\n", pthread_self(), num);
    sleep(1);
}


int main()
{
    //创建线程池
    ThreadPool* pool = ThreadPoolCreate(3, 10, 100);

    for (int i = 0; i < 100; i++)
    {
        int* num = (int*)malloc(sizeof(int));
        *num = i + 100;
        threadPoolAdd(pool,taskFunc,num);
    }
    sleep(30);
    ThreadPoolDestroy(pool);
    return 0;
}

threadpool.h:

#ifndef _THREADPOOL_H //如果不存在threadpool.h
#define _THREADPOOL_H //就引入threadpool.h

typedef struct ThreadPool ThreadPool;

//创建线程池并初始化
ThreadPool *ThreadPoolCreate(int min, int max, int queueSize);


//销毁线程池
int ThreadPoolDestroy(ThreadPool* pool);


//给线程池添加任务
void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg);

//获取线程池中工作的个数
int threadPoolBusyNum(ThreadPool* pool);

//获取线程池中活着的个数
int threadPoolAliveNum(ThreadPool* pool);

//工作(消费者)任务函数
void* workers(void* arg);

//管理线程任务函数
void* manager(void* arg);

//单个线程退出

 //否则不需要引入

#endif

threadpool.c:

#include"threadpool.h"
#include<pthread.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<unistd.h>

const int NUMBER = 2;
//任务结构体
typedef struct Task
{
	void(*function)(void* arg); // 函数指针,任务函数
	void* arg;                  //万能指针,任务函数参数
} Task;

//线程池结构体
typedef struct ThreadPool
{
	Task* taskQ;    //任务队列
	int queueCapacity;  //容量
	int queueSize;      //当前任务个数
	int queueFront;     //队头 -> 取数据
	int queueRear;      //队尾 -> 放数据

	pthread_t managerID;     //管理者线程ID
	pthread_t* threadIDs;    //工作的线程ID 
	int minNum;              //最小线程数
	int maxNum;              //最小线程数
	int busyNum;             //忙的线程个数
	int liveNum;             //存活的线程个数
	int exitNum;             //要销毁的线程个数
	pthread_mutex_t mutexPool;  //互斥锁pthread_mutex_t,锁整个线程池
	pthread_mutex_t mutexBusy;  //锁busyNum变量
	pthread_cond_t notFull;     // pthread _ cond _t表示多线程的条件变量,任务队列是不是满了
	pthread_cond_t notEmpty;    //任务队列是不是空了

	int shutdown;               //是不是要销毁线程池,销毁为1,不销毁为0
};

ThreadPool* ThreadPoolCreate(int min, int max, int queueSize)
{
	ThreadPool* pool = (ThreadPool*)malloc(sizeof(ThreadPool));
	do
	{
		if (pool == NULL)
		{
			printf("malloc threadpool fail...\n");
			break;
		}
		//开辟工作线程空间
		pool->threadIDs = (pthread_t*)malloc(sizeof(pthread_t) * max);
		if (pool->threadIDs == NULL)
		{
			printf("malloc threadIDs fail...\n");
			break;
		}
		memset(pool->threadIDs, 0, sizeof(pthread_t) * max);
		pool->minNum = min;
		pool->maxNum = max;
		pool->busyNum = 0;
		pool->liveNum = min;
		pool->exitNum = 0;

		//互斥锁、条件变量的初始化
		if (pthread_mutex_init(&pool->mutexPool, NULL) != 0 ||
			pthread_mutex_init(&pool->mutexBusy, NULL) != 0 ||
			pthread_cond_init(&pool->notFull, NULL) != 0 ||
			pthread_cond_init(&pool->notEmpty, NULL) != 0)
		{
			printf("mutex or cond init fail...\n");
			break;
		}

		//任务队列
		pool->taskQ = (Task*)malloc(sizeof(Task) * queueSize);
		pool->queueCapacity = queueSize;
		pool->queueSize = 0;
		pool->queueFront = 0;
		pool->queueRear = 0;

		pool->shutdown = 0;

		//创建管理者线程
		pthread_create(&pool->managerID, NULL, manager, pool);
		//创建工作线程
		for (int i = 0; i < min; i++)
		{
			pthread_create(&pool->threadIDs[i], NULL, workers, pool);
		}
		return pool;
	} while (0);

	//释放资源
	if (pool && pool->threadIDs)
	{
		free(pool->threadIDs);
	}
	if (pool && pool->taskQ)
	{
		free(pool->taskQ);
	}
	if (pool)
	{
		free(pool);
	}
	return NULL;
}


void threadPoolAdd(ThreadPool* pool, void(*func)(void*), void* arg)
{
	pthread_mutex_lock(&pool->mutexPool);
	while (pool->queueSize == pool->queueCapacity && !pool->shutdown)
	{
		//阻塞管理者(生产者)线程
		pthread_cond_wait(&pool->notFull, &pool->mutexPool);
	}
	if (pool->shutdown)
	{
		pthread_mutex_unlock(&pool->mutexPool);
		return;
	}

	//添加任务
	pool->taskQ[pool->queueRear].function = func;//
	pool->taskQ[pool->queueRear].arg = arg;
	pool->queueRear = (pool->queueRear + 1) % pool->queueCapacity;
	pool->queueSize++;
	pthread_cond_signal(&pool->notEmpty);//有新任务通知消费者
	pthread_mutex_unlock(&pool->mutexPool);

}

/*管理者线程主要负责创建线程和销毁线程*/
void* manager(void* arg)
{
	ThreadPool* pool = (ThreadPool**)arg;
	while (!pool->shutdown)
	{
		//每3秒检查一次
		sleep(3);

		//取出线程池中当前的任务数和活着的线程数(线程数是共享资源,操作里面的数据需要加锁)
		pthread_mutex_lock(&pool->mutexPool);
		int queueSize = pool->queueSize;
		int liveNum = pool->liveNum;
		pthread_mutex_unlock(&pool->mutexPool);


		//取出忙的线程数
		pthread_mutex_lock(&pool->mutexBusy);
		int busyNum = pool->busyNum;
		pthread_mutex_unlock(&pool->mutexBusy);

		//添加线程:
		//1.定义添加线程时机的规则: 任务个数>存活的线程个数 && 存活的线程个数<最大线程数
		if (queueSize > liveNum && liveNum < pool->maxNum)
		{
			int counter = 0;
			//切记加锁
			pthread_mutex_lock(&pool->mutexPool);
			for (int i = 0; i < pool->maxNum && counter < NUMBER && liveNum < pool->maxNum; ++i)
			{
				if (pool->threadIDs[i] == 0)
				{
					pthread_create(&pool->threadIDs[i], NULL, workers, pool);
					counter++;
					pool->liveNum++;

				}
			}
			pthread_mutex_unlock(&pool->mutexPool);
		}

		//销毁线程
		//1.定义销毁线程时机的规则: 忙的个数*2<存活的线程个数 && 存活的线程个数>最小线程数
		if (busyNum * 2 < liveNum && liveNum > pool->minNum)
		{
			//切记加锁
			pthread_mutex_lock(&pool->mutexPool);
			pool->exitNum = NUMBER;
			pthread_mutex_unlock(&pool->mutexPool);
			for (int i = 0; i < NUMBER; i++)
			{
				//这里只能让工作的线程自杀,不能主动销毁工作线程
				//使阻塞的线程脱离阻塞状态(pool->notEmpty)
				pthread_cond_signal(&pool->notEmpty);
			}
		}
	}
	return NULL;
}

void* workers(void* arg)
{
	ThreadPool* pool = (ThreadPool*)arg;
	while (1)
	{
		pthread_mutex_lock(&pool->mutexPool);

		//判断任务队列是否为空
		while (pool->queueSize == 0 && !pool->shutdown)
		{
			//阻塞线程
			pthread_cond_wait(&pool->notEmpty, &pool->mutexPool);

			//判断是不是要销毁线程
			if (pool->exitNum > 0)
			{
				pool->exitNum--;
				if (pool->liveNum > pool->minNum)
				{
					pool->liveNum--;
					pthread_mutex_unlock(&pool->mutexPool);
					threadExit(pool);
				}

			}
		}

		//判断线程池是否要关闭
		if (pool->shutdown)
		{
			pthread_mutex_unlock(&pool->mutexPool);
			threadExit(pool);
		}

		/*从任务列队中取出一个任务*/
		Task task;
		task.function = pool->taskQ[pool->queueFront].function;
		task.arg = pool->taskQ[pool->queueFront].arg;
		//每取出一个任务后需要移动任务队列的队头:(头指针+1)整除 队列大小 
		pool->queueFront = (pool->queueFront + 1) % pool->queueCapacity;
		pool->queueSize--;

		/*生产者生产了产品之后唤醒消费者,消费者消耗了产品之后唤醒生产者*/
		//消费了一个任务,需要唤醒生产者线程
		//pthread_cond_signal详解: https://blog.csdn.net/fanyun_01/article/details/106975364
		pthread_cond_signal(&pool->notFull);//消费了任务通知生产者
		pthread_mutex_unlock(&pool->mutexPool);

		//开始任务
		printf("thread % ld start working...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum++;
		pthread_mutex_unlock(&pool->mutexBusy);
		task.function(task.arg);
		free(task.arg);
		task.arg = NULL;

		//结束任务
		printf("thread % ld end working...\n", pthread_self());
		pthread_mutex_lock(&pool->mutexBusy);
		pool->busyNum--;
		pthread_mutex_unlock(&pool->mutexBusy);
	}
	return NULL;
}

void threadExit(ThreadPool* pool)
{
	//pthread_self(): https://blog.csdn.net/weixin_43778179/article/details/115219071
	pthread_t tid = pthread_self();
	for (int i = 0; i < pool->maxNum; i++)
	{
		if (pool->threadIDs[i] == tid)
		{
			pool->threadIDs[i] = 0;
			printf("threadExit() called,%ld exiting...\n", tid);
			break;
		}
	}
	pthread_exit(NULL);
}

int threadPoolBusyNum(ThreadPool* pool)
{
	pthread_mutex_lock(&pool->mutexPool);
	int busyNum = pool->busyNum;
	pthread_mutex_unlock(&pool->mutexPool);
	return busyNum;
}

int threadPoolAliveNum(ThreadPool* pool)
{
	pthread_mutex_lock(&pool->mutexPool);
	int aliveNum = pool->liveNum;
	pthread_mutex_unlock(&pool->mutexPool);
	return aliveNum;
}

int ThreadPoolDestroy(ThreadPool* pool)
{
	if (pool==NULL)
	{
		return -1;
	}
	//关闭线程池
	pool->shutdown = 1;
	//阻塞生产者
	pthread_join(pool->managerID, NULL);

	//唤醒阻塞的线程,被唤醒的线程会自动死亡,详情参照205行代码
	for (int i = 0; i < pool->liveNum; i++)
	{
		pthread_cond_signal(&pool->notEmpty);
	}
	//释放堆内存
	if (pool->taskQ)
	{
		free(pool->taskQ);
	}
	if (pool->threadIDs)
	{
		free(pool->threadIDs);
	}


	pthread_mutex_destroy(&pool->mutexBusy);
	pthread_mutex_destroy(&pool->mutexPool);
	pthread_cond_destroy(&pool->notEmpty);
	pthread_cond_destroy(&pool->notFull);
	free(pool);
	pool = NULL;
	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux 线程池 (C语言实现) 的相关文章

随机推荐

  • 基于51单片机的智能温控风扇(程序+仿真+原理图)

    目录 基于51单片机的智能温控风扇1 主要功能2 实验结果3 仿真工程4 原理图5 程序源码6 资源获取 基于51单片机的智能温控风扇 1 主要功能 基于51单片机的智能温控风扇 xff0c 通过DS180温度传感器采集温度 xff0c 并
  • 基于51单片机的八路竞赛抢答器设计

    目录 基于51单片机的八路抢答器设计1 主要功能2 仿真图3 测试图4 程序源码5 资源获取 基于51单片机的八路抢答器设计 1 主要功能 利用STC89C52单片机及外围接口实现的抢答系统 xff1b 在抢答过程中 xff0c 只有启动抢
  • 赛灵思-Zynq UltraScale+ MPSoC学习笔记汇总

    Zynq UltraScale 43 MPSoC学习目录 xff1a 1 赛灵思 Zynq UltraScale 43 MPSoCs xff1a 产品简介 2 赛灵思 Zynq UltraScale 43 MPSoC学习笔记 xff1a P
  • 数据可视化--实验五:高维非空间数据可视化

    声明 xff1a 本文CSDN作者原创投稿文章 xff0c 未经许可禁止任何形式的转载 xff0c 原文链接 文章目录 概要实验过程Pyecharts实验结果平行坐标系room1 6房间人员时长饼图 概要 学院 xff1a 计算机科学与技术
  • 7、AUTOSAR MCAL入门-实战:I/O驱动组

    7 AUTOSAR MCAL入门 实战 xff1a I O驱动组 在第三节中有介绍AUTOSAR 把MCAL 抽象分为4个驱动组 xff0c 分别为 xff1a 微控制器驱动组 xff0c 存储器驱动组 xff0c 通信驱动组 输入 输出驱
  • FreeRTOS学习笔记:FreeRTOS启动方式及流程

    FreeRTOS启动方式及流程 FreeRTOS有两种比较流行的启动方式 1 方式一 xff1a 在main函数中创建所有任务 具体说明 xff1a 在main函数中将硬件初始化 RTOS系统初始化 xff0c 创建所有的任务 xff0c
  • 树莓派4B与Android之缘——树莓派下LineageOS(Android 9)系统开机联网与远程控制

    一 树莓派连接屏幕 1 找到树莓派的micro hdmi口 xff0c 是视频图像的输出口 xff0c 见下图中的MICRO HDMI PORTS 2 连接屏幕 xff08 1 xff09 如果显示屏输入端口是HDMI xff0c 就用mi
  • Deep SDF 、NeuS学习

    DeepSDF Learning Continuous Signed Distance Functions for Shape Representation xff1a 学习用于形状表示的连续有符号距离函数 NeuS Learning Ne
  • layui 引入方式

    layui xff08 谐音 xff1a 类UI 是一款采用自身模块规范编写的前端 UI 框架 xff0c 遵循原生 HTML CSS JS 的书写与组织形式 xff0c 门槛极低 xff0c 拿来即用 其外在极简 xff0c 却又不失饱满
  • ubuntu20.04安装ROS及常见问题

    ubuntu20 04安装ROS及常见问题 一 ubuntu安装参考 xff08 双系统 xff09 1 ios镜像官网下载地址 xff1a https releases ubuntu com ga 61 2 239339907 18418
  • 在jetson xavier nx上配置orbslam3,带稠密重建

    这里写自定义目录标题 主要记录一下踩过的各种坑 xff0c 包括从配置开发板系统到配置orbslam3一条龙服务 xff0c 带cuda加速的opencv3 4 5开发板刷系统将系统移植到M 2硬盘上Sdkmanager安装cuda cud
  • ROS入门教程(五)—— RViz仿真

    上篇文章我们介绍了URDF文件的导出 xff0c 本文将继上文介绍安装完导出URDF文件后 xff0c 如何在机器人操作系统 ROS 中显示 xff0c 并且让它动起来 目录 前言 RViz机器人模型可视化 launch启动RViz配置文件
  • js几种继承

    提示 xff1a 主要是原型链继承 构造函数继承 原型链加构造函数继承 寄生组合式继承 一 原型链继承 子类想要继承父类的属性和方法 xff0c 可以将其原型对象指向父类的实例 xff0c 根据原型链就可以使用到父类的方法和属性 父类 fu
  • C++ 学习(基础语法篇)

    一 基础语法 1 1 C 43 43 简介 C 43 43 是一种静态类型的 编译式的 通用的 大小写敏感的 不规则的编程语言 xff0c 支持过程化编程 面向对象编程和泛型编程 C 43 43 是 C 的一个超集 xff0c 事实上 xf
  • 数据可视化--实验六:层次和网络可视化、文本可视化

    声明 xff1a 本文CSDN作者原创投稿文章 xff0c 未经许可禁止任何形式的转载 xff0c 原文链接 文章目录 概要实验过程Pyecharts实验结果邮件往来网络图职位树图邮件主题词云图 实验结论 概要 学院 xff1a 计算机科学
  • ssh连接windows10拒绝连接

    第一步 xff1a ssh使用的22端口 xff0c 首先确认windows10的22端口是否开启 开启步骤 1 控制面板 gt Windws Defender 防火墙 gt 高级设置 gt 入站规则 gt 新建规则 2 选择端口 gt 下
  • Jetson TX1 学习1 GPIO

    学习过程中为了防止遗忘 以此文字记录 如有错误 多多包涵 怕什么真理无穷 进一寸有一寸的欢喜 胡适 前置内容 xff1a Jetson GPIO 库 学习目标 xff1a 简单控制 Jetson TX1 官方载板 GPIO 引脚 学习内容
  • It was either not specified and/or could not be found for the javaType (java.util.List) : jdbcType

    在使用MyBatis Plus的时候 xff0c 他会将实体类以及表字段自动关联起来 xff0c 但是当我们想要指定额外的一对多关系的时候 xff0c 例如 xff1a 订单保存的时候同时需要保存订单详情列表 xff0c 此时订单与订单详情
  • WSL安装xfce4图像界面,并通过windows远程桌面登陆

    一 下载xorg xorg为X11的一个实现 xff0c xfce4需要 sudo apt install xrog 二 下载xfce4 sudo apt install xfce4 三 下载xrdp xrdp为远程连接软件 xff0c 默
  • linux 线程池 (C语言实现)

    线程池分为三个部分 xff1a 任务队列工作线程 xff0c N个 xff08 任务队列的消费者 xff09 管理者线程 xff0c 1个 主要实现的函数 xff1a 创建线程池线程池添加任务销毁线程池任务函数 xff08 做什么 xff0