linux:线程的控制

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》
文章目录
- 前言
- 一、线程的总结
- 1. 线程的优点
- 2. 线程的缺点
- 3. 线程异常
- 4.线程和进程
- 二、线程的控制
- 创建线程
- 线程终止
- 线程等待
- 获取返回值
- 线程分离
- 总结
前言
本文作为我对于线程的简单总结,线程控制的知识总结
一、线程的总结
1. 线程的优点
- 创建一个新线程的代价比创建一个新进程小的多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要小
- 线程占有的资源要比进程少很多
- 能充分利用多处理器的可并行数量(并行,多个执行流在同一时刻拿着不同的CPU继续运算,执行代码)
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用(如下载,上传),为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
创建线程并不是越多越好,进程的执行效率,一定随着线程的数量增多,效率为正态分布的(线程的切换)。线程的个数最好 = CPU的个数 * CPU的核数
2. 线程的缺点
- 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器,如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,如增加了额外的同步和调度开销,而可用的资源不变
- 健壮性(鲁棒性)降低:编写多线程需要全面更深入的考虑,在一个多线程程序里,因时间分配的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,也就是说线程之间是缺乏保护的
- 缺乏访问控制:因为线程共享地址空间,那一个线程定义的局部变量 or new出的空间,其它线程也能使用,这就导致一个线程可能会不小心修改另一个线程正在使用的数据,导致数据不一致、逻辑错误甚至程序崩溃
- 编程难度提高:编写与调试一个多线程程序比单线程程序困难
3. 线程异常
- 单个线程如果出现除0,野指针问题导致线程崩溃,进程也会崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也全部终止
创建5个线程,其中线程thread-4触发除0异常。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>
#include <vector>using func_t = std::function<void()>;
const int threadnum = 5;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f): threadname(name), createtime(time), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate *td = static_cast<ThreadDate *>(args);while (true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();if(td->threadname == "thread-4"){std::cout << td->threadname << "触发异常" << std::endl;int a = 10;a /= 0;}sleep(1);}
}int main()
{std::vector<pthread_t> pthreads;for (int i = 0; i < threadnum; ++i){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%d", "thread", i);pthread_t tid;ThreadDate *td = new ThreadDate(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);pthreads.push_back(tid);sleep(1);}while (true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

其中thread-4线程触发异常,进程直接终止。
4.线程和进程
- 进程是资源分配的基本单位
- 线程是调度的基本单位
- 线程共享进程数据,但也有自己的一部分独立的数据(线程ID,线程的上下文数据,独立的栈结构(pthread库维护),errno,信号屏蔽字,调度优先级)
- 进程的多个线程共享同一个地址空间,因此地址空间的代码段,数据段都是共享的,线程还共享进程的文件描述符表,每种信号的处理方式(SIG_IGN,SIG_DFL,自定义的信号处理函数),当前工作目录(cwd),用户id和组id
进程和线程的关系如下:

二、线程的控制
创建线程

函数原型: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:thread:返回线程ID
attr:设置线程属性,attr为nullptr表示使用默认属性
start_routine:是函数指针,为线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码
错误检查:传统的一些函数,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。pthreads函数出错时不会设置全局变量errno,而是将错误码通过返回值返回。pthreads也同样提供线程内errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值的判定,因为读取返回值要比读取线程内的errno变量更方便。
下面我们要创建一个新线程循环打印"new thread’,主线程循环打印"main thread"
#include <iostream>
#include <pthread.h>
#include <unistd.h>void *threadRoutine(void *args)
{while(true){std::cout << "new thread" << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, nullptr);while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

编译时会有报错,编译器不认识pthread_create这个函数,但我们不是加了头文件吗?
我们知道linux没有真正的线程,只有轻量级进程的概念,所以操作系统只会提供轻量级进程创建的系统调用,不会直接提供线程创建的接口。linux为了解决这一问题,就在软件层创建了pthread原生线程库对用户提供线程控制接口,在内部将线程与轻量级进程一一对应。

这样有什么好处?标准化和兼容性(pthread编写的多线程应用程序在遵循POSIX标准的各种Unix-like系统上(包括Linux)都具有很高的可移植性),灵活性:pthread库提供了一套丰富的API,用于线程的创建、管理、同步和通信。这使得开发者可以根据应用程序的需求灵活地创建和管理线程,实现复杂的并发和并行计算任务。






结果如上。
如何给创建的线程传参?

如上,arg的类型是void*类型,表示任意类型的指针。也就是说,我们可以穿一个整形,字符串,结构体对象。如下传递结构体。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>using func_t = std::function<void()>;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f):threadname(name), createtime(time), func(f){}
public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate * td = static_cast<ThreadDate*>(args);while(true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime<< std::endl;td->func();sleep(1);}
}int main()
{pthread_t tid;ThreadDate * td = new ThreadDate("thread-1", (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);while(true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}

那如何一次创建多个线程?与创建多个子进程类似。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <functional>
#include <time.h>
#include <vector>using func_t = std::function<void()>;
const int threadnum = 5;class ThreadDate
{
public:ThreadDate(const std::string &name, const uint64_t &time, func_t f): threadname(name), createtime(time), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "执行的任务..." << std::endl;
}void *threadRoutine(void *args)
{ThreadDate *td = static_cast<ThreadDate *>(args);while (true){std::cout << "new thread "<< " name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();sleep(1);}
}int main()
{std::vector<pthread_t> pthreads;for (int i = 0; i < threadnum; ++i){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%d", "thread", i);pthread_t tid;ThreadDate *td = new ThreadDate(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, threadRoutine, (void *)td);pthreads.push_back(tid);sleep(1);}while (true){std::cout << "main thread" << std::endl;sleep(1);}return 0;
}


下面代码,创建线程并通过返回值打印其线程id。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread, sub thread: " << tid << std::endl;sleep(1);}return 0;
}


很明显,创建出来的线程id与LWP并不同,那主线程的id值是否也与LWP不同?这我们就要了解一个接口pthread_self(线程获取自己的id)

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << "thread id is " << pthread_self() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << pthread_self() << "sub thread id " << tid << std::endl;sleep(1);}return 0;
}


主线程的id与新线程的id明显不是LWP,那是什么呢?我们以十六进制打印看看。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

现在,这个线程id是不是很像地址? 没错,线程id就是地址。
线程终止
如果需要只终止某个线程而不终止整个进程,可以有三个方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit函数
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}


- 线程可以调用pthread_exit终止自己

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}//return nullptr; // 线程终止pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}


- 一个线程可以调用pthread_cancel终止同一进程中的另一个线程

- 功能:取消一个执行中的线程
- 参数 thread:线程id
- 返回值:成功返回0,失败返回错误码
不能用exit函数来终止线程,这会导致这个进程被终止。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}//return nullptr; // 线程终止exit(13);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread-1");while(true){std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}


线程等待
获取返回值
我们知道子进程退出时会有僵尸状态,需要父进程来等待子进程。那线程也要被等待吗?是的,线程也要被等待。线程退出没有等待,也会导致类似进程的僵尸问题。线程通过等待获取新线程的返回值。

- thread:线程id
- retval:指向一个指针,后者指向返回值
- 返回值:等待成功返回0,失败返回错误码
调用该函数的线程将以阻塞的方式等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join的终止状态是不同的: - 如果thread线程通过return返回,retval所指向的单元存放的是thread线程函数的返回值
- 如果thread线程被别的线程调用pthread_cancel异常终止,retval所指向的单元存放的是常数PTHREAD_CANCELED
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数
- 如果不想获取thread的返回值,可以将nullptr传给retval
以从线程return为例。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread-1");std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;int n = pthread_join(tid, nullptr);std::cout << "main thread done , n : " << n << std::endl;return 0;
}


线程分离
对于一个线程,如果主线程要等待它,则会阻塞等待导致主线程自己的任务效率低。如果主线程不等待它,则可能导致类似僵尸进程的状态,使资源泄漏。这时我们就可以将线程分离。
线程可以设置为分离属性,一个线程如果被设置为分离属性,则该线程在退出之后,不需要其它执行流回收该线程资源,而是由操作系统进行回收。
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成资源泄漏
- 如果不关心线程的返回值,join是一种负担,我们可以告诉系统,当线程退出时,自动释放线程资源。
- joinable和分离是冲突的,一个线程不能既是joinable又是分离的。如果等待一个分离的线程,函数会立即返回错误,错误码通常为EINVAL

- thread:线程id
- 返回值:成功返回0,失败返回错误码
分离的线程在同一个进程地址空间,相互的线程不会相互干扰,但是如果分离的线程崩溃,进程也会崩溃。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>// 转换为十六进制
std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);int cnt = 5;while (cnt--){std::cout << "new thread, name: " << name << "thread id is " << ToHex(pthread_self()) << std::endl;sleep(1);}int a = 10;a /= 0; // 除0异常return nullptr; // 线程终止
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread-1");std::cout << "main thread id " << ToHex(pthread_self()) << std::endl;sleep(10);pthread_cancel(tid);// int n = pthread_join(tid, nullptr);// std::cout << "main thread done , n : " << n << std::endl;return 0;
}


总结
以上就是我对于线程控制的总结。

相关文章:
linux:线程的控制
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程的总结1. 线程的优点2. 线程的缺点3. 线程异常4.线程和进程 二、线程的控制创建线程线程终止线程等待获取返回值 线程分离 总结 前言 本文作为我对于线程的…...
小程序分账方案:实现商户分账的简便与灵活
随着移动支付的普及和小程序的快速发展,越来越多的商家选择在微信小程序上开展业务。然而,对于一些有多个分账方的商户而言,如何实现快速、准确和灵活的资金分账成为了一个挑战。本文将介绍一种高效的小程序分账方案,帮助商户轻松…...
Python数值微积分,摆脱被高数支配的恐惧
文章目录 差分和累加积分多重积分 Python科学计算:数组💯数据生成 差分和累加 微积分是现代科学最基础的数学工具,但其应用对象往往是连续函数,而其在非连续函数的类比,便是差分与累加。在【numpy】中,可…...
使用express+nginx+pm2+postman实现推送zip包自动更新前端网页
1.nginx配置将80端口代理到项目的3000端口 server {listen 80; #监听的端口server_name localhost; #监听的域名#charset koi8-r;#access_log logs/host.access.log main;location / {#root html;#index index.html index.html;proxy_pass http://127.0.0.1:3000; #转…...
如何在小程序中绑定身份证
在小程序中绑定身份证信息是一项常见的需求,特别是在需要进行实名认证或者身份验证的场景下。通过绑定身份证信息,可以提高用户身份的真实性和安全性,同时也为小程序提供了更多的个性化服务和功能。下面就介绍一下怎么在小程序中绑定居民身份…...
【机器学习】【决策树】分类树|回归树学习笔记总结
决策树算法概述 基本概念 决策树:从根节点开始一步步走到叶子节点,每一步都是决策过程 对于判断的先后顺序把控特别严格 一旦将判断顺序进行变化则最终的结果将可能发生改变 往往将分类效果较佳的判断条件放在前面,即先初略分在进行细节分…...
运维随录实战(14)之docker搭建mysql主从集群(Replication))
1, 从官方景镜像中拉取mysql镜像: docker pull mysql:8.0.24 --platform linux/x86_64 2, 创建master和slave容器: 在创建之前先设置网段 docker network create --subnet=172.20.0.0/24 soil_network master: docker run -d -p 3306:3306 --name mysql-master --net soi…...
CI/CD笔记.Gitlab系列:2024更新后-设置GitLab导入源
CI/CD笔记.Gitlab系列 设置GitLab导入源 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_…...
一款Mac系统NTFS磁盘读写软件Tuxera NTFS 2023 for Mac
当您获得一台新 Mac 时,它只能读取 Windows NTFS 格式的 USB 驱动器。要将文件添加、保存或写入您的 Mac,您需要一个附加的 NTFS 驱动程序。Tuxera 的 Microsoft NTFS for Mac 2023是一款易于使用的软件,可以在 Mac 上打开、编辑、复制、移动…...
Error while Deploying HAP
第一个程序就遇到这么恶心的bug,也查了很多类似的问题是什么情况,后来无意中菜解决了这个bug,确实也是devicps下面加一个参数,但是找了半天 这是我遇到这个问题的解决办法。其他解决办法如下: https://blog.51cto.com…...
多线程扩展:乐观锁、多线程练习
悲观锁、乐观锁 悲观锁:一上来就加锁,没有安全感,每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差。 乐观锁:一开始不上锁,认为是没有问题的,等要出现线程安全问题的时…...
代码随想录day31 Java版
今天开始刷动态规划,先拿简单题练手 509. 斐波那契数 class Solution {public int fib(int n) {if (n < 1) return n; int[] dp new int[n 1];dp[0] 0;dp[1] 1;for (int index 2; index < n; index){dp[index] dp[index - 1] dp[index -…...
linux系统adb调试工具
adb的全称为Android Debug Bridge,就是起到调试桥的作用。通过adb可以在Eclipse中通过DDMS来调试Android程序,说白了就是调试工具。 adb的工作方式比较特殊,采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯,默认情况下adb会…...
【Golang星辰图】全面解析:Go语言在Web开发中的顶尖库和框架
创造无限可能:探索Go语言在Web开发中的强大库和框架 前言 Go语言作为一门简洁、高效的编程语言,在Web开发领域也展现出了强大的潜力。本文将会带您深入了解Go语言在Web开发中的相关库和框架,以及它们的优势和使用方法。通过学习这些内容&am…...
CSS 居中对齐 (水平居中 )
水平居中 1.文本居中对齐 内联元素(给容器添加样式) 限制条件:仅用于内联元素 display:inline 和 display: inline-block; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…...
数据结构:图及相关算法讲解
图 1.图的基本概念2. 图的存储结构2.1邻接矩阵2.2邻接表2.3两种实现的比较 3.图的遍历3.1 图的广度优先遍历3.2 图的深度优先遍历 4.最小生成树4.1 Kruskal算法4.2 Prim算法4.3 两个算法比较 5.最短路径5.1两个抽象存储5.2单源最短路径--Dijkstra算法5.3单源最短路径--Bellman-…...
【c++设计模式06】创建型4:单例模式(Singleton Pattern)
【c++设计模式06】创建型4:单例模式(Singleton Pattern) 一、定义二、适用场景三、确保,一个类可以实例化一个对象四、分类1、懒汉式——首次访问时才创建实例2、饿汉式——类加载时就创建实例五、线程安全性深入讨论(懒汉式单例模式)1、懒汉式单例真的线程不安全吗?——…...
Python-OpenCV-边缘检测
摘要: 本文介绍了使用Python和OpenCV进行边缘检测的方法,涵盖了基本概念、核心组件、工作流程,以及详细的实现步骤和代码示例。同时,文章也探讨了相关的技巧与实践,并给出了常见问题与解答。通过阅读本文,…...
C#中使用 Prism 框架
C#中使用 Prism 框架 前言一、安装 Prism 框架二、模块化开发三、依赖注入四、导航五、事件聚合六、状态管理七、测试 前言 Prism 框架是一个用于构建可维护、灵活和可扩展的 XAML 应用程序的框架。它提供了一套工具和库,帮助开发者实现诸如依赖注入、模块化、导航…...
什么是线程池,线程池的概念、优点、缺点,如何使用线程池,最大线程池怎么定义?
线程池(Thread Pool)是一种并发编程中常用的技术,用于管理和重用线程。它由线程池管理器、工作队列和线程池线程组成。 线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...
