【Linux】线程Thread

news2025/2/24 23:44:15

🔥博客主页: 我要成为C++领域大神
🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

线程概述

线程是进程中的一个执行单元

经典的并发模型,多进程模型,占用大量的内存开销,庞大的进程间调度开销,提供一种开销更小,更轻量级的并发解决方案,多线程技术。

线程在进程中,与进程共享资源和数据,进程是容器,线程是执行单元,进程退出可能导致所有线程退出。

每个进程中都有一个主控线程,还有其他的普通线程,操作系统会给进程分配需要的内存单元。

不支持线程技术的操作系统可以忽略线程概念,进程是调度单位。

多线程操作系统下,进程是内存管理单位,线程才是唯一的调度单元。

线程的CPU分配

Linux操作系统下,线程就是轻量级进程,每个进程分配一个LWP

在Linux操作系统下,所有的调度单位都是进程,淡化线程概念

线程的实现方式

用户线程(User Thread):在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理。可以利用第三方库的形式在不支持线程技术的系统下安装和使用线程,用户级线程的调度开销更小,因为大部分的操作都在用户层完成,无需内核干预

内核线程(Kernel Thread):操作系统中每创建一个线程,系统都会为其创建一个内核对象,此线程系统可以识别支持,会为线程执行资源(时间片)。Control控制器会为每个内核级线程创建内核对象,每个内核级线程都会由CPU进行时间片切换

轻量级进程(LightWeight Process):在内核中来支持用户线程;

进程的蜕化

进程内存资源独占,不与其他人共享,进程是独立的调度单位(无需考虑线程问题)

进程中出现了多个执行单元,讨论考虑线程问题

讨论和分析的蜕化,讨论线程,进程的讨论变为主线程和普通线程的分析和讨论

线程间的共享资源和非共享资源

PCB:共享资源

栈:线程栈空间非共享,每个线程创建后,会分配8MB线程栈

库:库资源共享

堆:共享堆空间

全局资源和静态资源:全局资源共享

代码段:代码段共享

文件描述符表:文件描述符表共享

信号的处理行为(某个线程改变信号行为)对所有线程共享。信号屏蔽字非共享,普通线程拥有独立的屏蔽字,拷贝继承于主线程

TCB:每个线程拥有自己的线程控制块,独立的tid

线程开发相关API接口

NPTL线程库

NPTL线程库是典型的内核级线程库,创建的线程可以被系统标识分配cpu资源(轻量级进程)

native Posix thread library

相关命令

ps aux 进程查看

ps ajx 进程关系查看

ps -eLf 所有线程查看 PID相同的是一组线程

LWPPID 相同的情况表示该进程只有一个线程。

ps -Lf pid 查看某一个进程下的线程

每个线程都会被系统分配lwp 调度编号, 便于系统管理调度线程,但是线程拥有独立的tid,线程id

头文件和函数

#include <pthread.h>使用线程函数的头文件

pthread_t tid线程tid类型

pthread_create

创建线程并指定类型pthread_create(pthread_t *tid,NULL,void * (thread_job)(void *),void *arg)第一个参数是线程id,第二个是线程属性,第三个参数是线程工作地址,第四个参数是线程工作参数。
返回值是err

下面是使用pthread_create创建线程的简单demo程序,通过PIDLWP相同可以看出,这个线程就是主控线程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程被创建,参数为%d\n",*(int*)arg);
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    //普通线程都是由主控线程创建的
    pthread_create(&tid,NULL,thread_job,(void*)&code);
        printf("普通线程被主控线程创建出来\n");
    while(1) sleep(1);
    return 0;
}

在编译时需要链接库

gcc Pthrad_Create.c -lpthread -o app

关于线程函数的错误处理

线程函数出错,会返回错误号(err>0),使用char *errstr=strerror(err)进行错误判断

下面是打印错误日志的demo程序:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程被创建,参数为%d\n",*(int*)arg);
    while(1) sleep(1);
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    //普通线程都是由主控线程创建的
    while(1)
    {
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
    else
    {
        printf("线程%d成功创建\n",++flags);
    }
    }

    return 0;
}

32位Ubuntu操作系统下,一个进程只能创建381个线程

pthread_self

pthread_tid =pthread_self(void) 成功返回当前线程的id

主线程通过pthread_create创建普通线程,成功传出tid与普通线程自身利用pthread_self()得到的tid值相等但是进程状态不相等,因为pthread_self()获取tid时可以保证当前的有效性,主线程在获取tid的时候,普通线程可能已经退出。


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    return NULL;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        while(1) sleep(1);//主控线程还在运行,普通线程已经退出
    return 0;
}

pthread_join(pthread_t tid,void **retval)

线程回收函数,可以回收线程资源的同时获取线程的返回值。经典的阻塞回收函数,会一致等待普通线程结束后,进行回收。


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>


void * thread_job(void *arg)//普通线程的创建
{
    printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    return (void*)126;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        pthread_join(tid,&revtal);
        printf("Thread Exit Code:%ld\n",(long int)revtal);
    return 0;
}

可以看到,如果线程工作函数可以正常退出,退出码就是线程工作函数的返回值

pthread_cancel(pthread_t tid)

指定线程tid,取消结束线程

下面是简单的取消一个线程的demo程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>

void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        printf("进程正在工作。。。\n");
        sleep(1);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();
    
    return 0;
}

线程被成功结束,这个进程下只有一个主控线程在工作

当我们对代码稍作修改:

删除线程工作函数中的调用系统函数:

void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();      
    return 0;
}

结果显示,pthread_cancel并没有成功取消线程。

这有些类似于信号,信号处理的三个切换条件:系统调用,软件中断,软件异常。而pthread_cancel取消事件的处理条件,必须有系统调用。

有一个专门为pthread_cancel提供系统调用的空函数:void pthread_testcancel(void),提供一次空的调用。

在线程工作函数加入这个函数再次尝试:

void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        pthread_testcancel(void);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pause();      
    return 0;
}

可以看到只要一个主控线程,普通线程被结束了。

当我们对线程进行取消后,回收一下资源打印退出码看一下:

void * thread_job(void *arg)//普通线程的创建
{
    //printf("普通线程tid %x 创建成功\n",(unsigned int)pthread_self());
    while(1)
    {
        pthread_testcancel(void);
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        sleep(5);
        pthread_cancel(tid);
        pthread_join(tid,&revtal);
        printf("Thread Exit Code:%ld\n",(long int)revtal);
        pause();      
    return 0;
}

可以看到退出码是-1,所以线程指定退出码时不允许使用-1,保留给pthread_cancel

pthread_detach(pthread_t tid)

用于将一个线程设置为分离态线程。

可以通过线程属性,批量创建分离态线程,这些线程诞生即是分离态。

线程有回收态和分离态,这两种状态是互斥的。

若修改线程退出状态,从回收态改为分离态,此操作不可逆,不能将分离线程变为回收态

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <pthread.h>

void * thread_job(void *arg)//普通线程的创建
{
    pthread_detach(pthread_self());
    while(1)
    {
        pthread_testcancel();
    }
    return (void*)129;
}

int main()
{
    pthread_t tid;
    int code=1024;
    int flags=0;
    int err;
    void *revtal=NULL;
    //普通线程都是由主控线程创建的
    if((err=pthread_create(&tid,NULL,thread_job,(void*)&code))>0)
    {
        printf("thread create failed:%s\n",strerror(err));
        exit(0);//创建失败,进程退出
    }
        printf("Monster Thread Tid:%x 普通线程Tid:%lx \n",(unsigned int)pthread_self(),tid);
        if((err=pthread_join(tid,&revtal))>0)
        {
            printf("join call failed %s\n",strerror(err));
        }
        printf("Thread Exit Code:%ld\n",(long int)revtal);
        pause();
        return 0;
}

joindetach会竞争时间片,所以有时线程以detach分离态被创建,当线程处于分离态时,无法手动回收,join执行失败

如果对一个分离态线程进行回收操作,pthread_join会失效返回。对一个已经处于回收阶段(join已经开始阻塞等待了)的线程设置分离,分离设置失败。

一个自行回收,一个由系统回收,一个可以得到线程退出码,一个无法获取

pthread_exit(void * exitcode)

线程退出函数,结束当前线程,与进程无关,退出码可以被join获取

线程的退出方式

return 0

主线程结束进程;结束普通线程,返回结果

pthread_cancel

主线程结束,与进程无关

普通进程结束,与进程无关

使用普通线程对主线程进行回收,产生了僵尸级线程

pthread_exit()

主线程结束,与进程无关

普通进程结束,与进程无关

exit()

进程退出,释放所有线程

线程属性

在使用创建线程函数时指定线程类型pthread_create(pthread_t *tid,pthread_attr_t *attr,void * (thread_job)(void *),void *arg)

用第二个参数指定线程属性

pthread_attr_t线程属性类型,是一个结构体类型

修改线程程属性的操作

使用pthread_attr_getdetachstate查看线程默认退出属性

使用pthread_attr_getstack查看栈属性


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>


int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);
    return 0;
}

使用pthread_attr_setdetachstate修改线程默认退出状态为分离态

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>

void *thread_job(void * arg)
{

}

int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);

    pthread_t tid;

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    pthread_create(&tid,&attr,thread_job,NULL);
    int err=pthread_join(tid,NULL);
    if(err>0)
        printf("Join Call Failed:%s\n",strerror(err));
    return 0;
}

使用pthread_attr_setstacksize修改默认线程栈大小

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>
#include <sys/fcntl.h>
#include <signal.h>
#include <pthread.h>

void *thread_job(void * arg)
{

}

int main()
{
    int State;
    void *stack_addr;//栈空间地址
    size_t stacksize;//栈空间默认大小
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_getdetachstate(&attr,&State);
        if(State==PTHREAD_CREATE_JOINABLE)
        printf("Thread Default State is Join\n");
    else
        printf("Thread Default State is Detach\n");
    pthread_attr_getstack(&attr,&stack_addr,&stacksize);
    printf("线程栈地址为%p 栈大小为%d\n",stack_addr,stacksize);

    pthread_t tid;

    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    stacksize=0x100000;
    int err;
    int flags=0;
    while(1)
    {
        if((stack_addr=(void*)malloc(stacksize))==NULL)
        {
            printf("malloc Stack Failed\n");
        }
        pthread_attr_setstacksize(&attr,stacksize);
        if((err=pthread_create(&tid,&attr,thread_job,NULL))>0)
        {
            printf("Create failed:%s\n",strerror(err));
            exit(0);
        }
        printf("Thread %d 创建成功\n",++flags);
    }
    return 0;
}

由结果可知,当将默认大小变小后,32位系统下可以创建的线程数量增加了。

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

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

相关文章

ROS学习(17):定位和地图绘制(1)

目录 0.前言 1.定位和建图 1.里程计&#xff08;Odometry&#xff09; 2.扫描匹配&#xff08;Scan Matching&#xff09; 3.结尾 0.前言 好久不见各位&#xff0c;前段时间忙着考试&#xff08;6级和一些专业课&#xff09;和摆烂断更了近30天&#xff0c;现在哥们回来更…

【移动应用开发期末复习】第五/六章

系列文章 第一章——Android平台概述 第一章例题 第二章——Android开发环境 第二章例题 第三章 第三章例题 第四章 系列文章界面布局设计线性布局表格布局帧布局相对布局约束布局控制视图界面的其他方法代码控制视图界面数据存储与共享首选项信息数据文件SQLite数据库Content…

【Linux】锁|死锁|生产者消费者模型

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ ​ 访问互斥 …

VOC格式转YOLO格式,xml文件转txt文件简单通用代码

目录 前言 思路介绍 代码 完整代码 拓展代码 前言 很多人在进行目标检测训练时习惯将得到的数据标注为XML文件的VOC格式&#xff0c;或者在网上获取的数据集被标注为XML文件&#xff0c;但是不同的标注工具进行的标注会产生不同的标注xml文件&#xff0c;这里我写了一种通用…

Ruby langchainrb gem and custom configuration for the model setup

题意&#xff1a;Ruby 的 langchainrb gem 以及针对模型设置的自定义配置 问题背景&#xff1a; I am working in a prototype using the gem langchainrb. I am using the module assistant module to implemente a basic RAG architecture. 我正在使用 langchainrb 这个 ge…

初识Java(二)

初识Java的main方法 1.1 main方法示例 public class world {public static void main(String[] args) {System.out.println("hello,world!");}}通过上述代码&#xff0c;我们可以看到一个完整的Java程序的结构&#xff0c;Java程序的结构由如下三个部分组成&#x…

标签接口开发(富含完整CRUD开发流程)

文章目录 1.easyCode生成CRUD1.生成代码2.查看代码3.调整代码1.SubjectLabelDao.xml发现生成的select语句不带逗号&#xff01;&#xff01;&#xff01;1.解决方法&#xff1a;2.entity.java.vm3.dao.java.vm4.Mapper.xml.vm 2.重新生成代码3.SubjectLabelDao.java 删除Pageab…

11-Django项目--Ajax请求二

目录 模版: demo_list.html perform_list.html 数据库操作: 路由: 视图函数: Ajax_data.py perform.py 模版: demo_list.html {% extends "index/index.html" %} {% load static %} # 未实现修改,删除操作{% block content %}<div class"container…

nacos在k8s上的集群安装实践

目录 概述实践nfs安装使用 k8s持久化nacos安装创建角色部署数据库执行数据库初始化语句部署nacos ingress效果展示 结束 概述 本文主要对 nacos 在k8s上的集群安装 进行说明与实践。主要版本信息&#xff0c;k8s: 1.27.x&#xff0c;nacos: 2.0.3。运行环境为 centos 7.x。 实…

江协科技51单片机学习- p19 串口通信

前言&#xff1a; 本文是根据哔哩哔哩网站上“江协科技51单片机”视频的学习笔记&#xff0c;在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了江协科技51单片机教学视频和链接中的内容。 引用&#xff1a; 51单片机入门教程-2…

力扣:59. 螺旋矩阵 II(Java,模拟)

目录 题目描述示例 1&#xff1a;代码实现 题目描述 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5…

node mySql 实现数据的导入导出,以及导入批量插入的sql语句

node 实现导出, 在导出excel中包含图片&#xff08;附件&#xff09; node 实现导出, 在导出excel中包含图片&#xff08;附件&#xff09;-CSDN博客https://blog.csdn.net/snows_l/article/details/139999392?spm1001.2014.3001.5502 一、效果 如图&#xff1a; 二、导入 …

此消彼长之间,国货品牌如何“进化”?

2024年&#xff0c;国内运动鞋服行业各品牌的股价集体回暖。年初至今&#xff08;1月2日至6月26日&#xff09;&#xff0c;港股的四大运动品牌中&#xff0c;361度涨幅达30.55%&#xff0c;特步上涨19.1%&#xff0c;安踏上涨7.75%&#xff0c;而李宁与美股市场的耐克组成了“…

【ARM】内存属性Memory Attributes (MemAttr)

目录 1. EWA 2. Device 3. Cacheable 4. Allocate 5. 内存属性的传播 6. 事务属性组合 7. Memory Type 内存属性Memory Attributes (MemAttr) 包含Early Write Acknowledgment (EWA), Device, Cacheable, 以及Allocate。 1. EWA EWA&#xff0c;Early Write Acknowledg…

Java进阶-Lambda

Java进阶-Lambda 前言Lambda表达式什么是Lambda表达式初识Lambda表达式Lambda表达式的简单使用Lambda表达式格式分析与传统接口方法实现的比较 理解Lambda表达式函数式编程非纯函数实例纯函数示例函数式编程在Lambda表达式中的体现 闭包闭包与Lambda表达式的示例 类型推导-匿名…

裸机与操做系统区别(RTOS)

声明&#xff1a;该系列笔记是参考韦东山老师的视频&#xff0c;链接放在最后&#xff01;&#xff01;&#xff01; rtos&#xff1a;这种系统只实现了内核功能&#xff0c;比较简单&#xff0c;在嵌入式开发中&#xff0c;某些情况下我们只需要多任务&#xff0c;而不需要文件…

【插件】IDEA这款插件Key Promoter X,爱到无法自拔

文章目录 为什么选择Key Promoter X&#xff1f;1. 提升开发效率2. 友好的学习曲线3. 可定制性强 安装和配置Key Promoter X1. 安装插件2. 配置插件 使用Key Promoter X个人使用体验1. 快捷键记忆2. 定制化功能3. 整体体验提升 总结 &#x1f389;欢迎来到Java学习路线专栏~探索…

Excel中的“点选输入”——次级下拉列表创建

在Excel中&#xff0c;用“数据验证”功能可以设置下拉列表&#xff0c;二级下拉列表需要设置公式。 (笔记模板由python脚本于2024年06月16日 18:36:37创建&#xff0c;本篇笔记适合经常使用Excel处理数据的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;http…

iOS 实现类似抖音滚动效果

效果图 思路 整体上我们使用tableView实现&#xff0c;为了预留内容的缓冲&#xff0c;我们将tableView 的contentinset设置为上面一个屏幕的高度&#xff0c;下面一个屏幕的高度&#xff0c;左右为0&#xff0c;这样保证我们滚动过去的时候 都是准备好的内容 然后就是滑动效果…

创新与责任并重!中国星坤连接器的可持续发展战略!

在当今全球化的商业环境中&#xff0c;企业的社会责任、技术创新和产品质量是企业可持续发展的三大支柱。中国星坤正是这样一家企业&#xff0c;它在电子连接技术领域以其卓越的技术创新、坚定的环保责任和严格的生产品控而著称。本文将深入探讨星坤科技如何通过其FAE技术团队的…