嵌入式开发学习--进程、线程

什么是进程

进程和程序的区别

概念
程序:编译好的可执行文件,存放在磁盘上的指令和数据的有序集合(文件),程序是静态的,没有任何执行的概念。
进程:一个独立的可调度的任务,执行一个程序所分配的资源的总称,进程是程序的一次执行过程,进程是动态的,包括创建、调度、执行和消亡。

进程特点
(1)系统会为每个进程分配0-4g的虚拟空间,0-3g(用户空间)是每个进程所独有的,3g-4g(内核空间)是所有进程所共有的。
图片来源于网络,侵权立删
(2)CPU调度进程时会给进程分配时间片(几毫秒~十几毫秒),时间片用完后,cpu再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作。(没有外界干预的情况下怎么调度进程是CPU随机分配的 )。
进程段
Linux中的进程大致包含三个段:
数据段:存放的是全局变量、常数以及动态数据分配的数据空间(如malloc函数取得的空间)等。
正文段:存放的是程序中的代码
堆栈段:存放的是函数的返回地址、函数的参数以及程序中的局部变量 (类比内存的栈区)。
进程分类
**交互进程:**该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑器等
**批处理进程:**该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。(目前接触不到)
**守护进程:**该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。

进程状态

D uninterruptible sleep (usually IO) 不可中断的睡眠态
R running or runnable (on run queue) 运行态
S interruptible sleep (waiting for an event to complete) 可中断的睡眠态
T stopped by job control signal 暂停态
t stopped by debugger during the tracing 因为调试而暂停
X dead (should never be seen) 死亡态
Z defunct (“zombie”) process, terminated but not reaped by its parent 僵尸态

< high-priority (not nice to other users) 高优先级
N low-priority (nice to other users) 低优先级
L has pages locked into memory (for real-time and custom IO) 锁在内存中
s is a session leader 会话组组长
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)多线程
+ is in the foreground process group 前台进程
进程切换图
图片来源于网络,侵权立删
进程创建后,进程进入就绪态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。

进程相关命令
ps 查看系统中运行的进程-aux -ef
top 动态显示系统中运行的进程
renice 改变正在运行的进程的优先级
nice 按用户指定的优先级运行进程
kill 给进程发信号
bg 将进程切换到后台
fg 将进程切换到前台
jobs 查看当前终端的进程

进程函数接口

创建进程fork()

pid_t fork(void);
功能:创建子进程
返回值:
    成功:在父进程中:返回子进程的进程号 >0
         在子进程中:返回值为0
    失败:-1并设置errno
#include <unistd.h>
#include<stdio.h>

int main(int argc, char const *argv[])
{
    pid_t pid = fork(); //创建一个子进程

    if(pid<0)
    {
        perror("fork err");
        return -1;
    }
    else if(pid==0)  //子进程
    {
        printf("i am child\n");
        while(1);  //让子进程不要结束
    }
    else    //父进程
    {
        printf("i am parent\n");
        while(1);  //让父进程不要结束
    }
    return 0;
}

执行:./a.out
图片来源于网络,侵权立删
特点:
1)子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但它们的PID、PPID是不同的。
2)父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互不影响。
3)若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
4)若是子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程的产生)。
回收资源

pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
              失败:-1

pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
    pid:>0     指定子进程进程号
         =-1   任意子进程
         =0    等待其组ID等于调用进程的组ID的任一子进程
         <-1   等待其组ID等于pid的绝对值的任一子进程
    status:子进程退出状态,不接受子进程状态设为NULL
    options:0:阻塞 WNOHANG:非阻塞
返回值:正常:结束的子进程的进程号
      当使用选项WNOHANG且没有子进程结束时:0
      出错:-1
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    pid_t pid = fork(); //创建一个子进程

    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0) //子进程
    {
        printf("i am child\n");
        sleep(3);
    }
    else //父进程
    {
        printf("i am parent\n");
        //wait(NULL);
        //waitpid(-1,NULL,0); //此时和wait(NULL)一样,0代表阻塞。
        //waitpid(-1,NULL,WNOHANG);  //WNOHANG代表非阻塞,此时有可能接收不到子进程资源从而产生僵尸

        while (1) //所以用WNOHANG的时候需要通过循环调用
        {
            if (waitpid(-1, NULL, WNOHANG) > 0) //直到接收到了子进程资源就结束
                break;
        }
    }
    return 0;
}

结束进程

void exit(int status);
功能:结束进程,刷新缓存

void _exit(int status);
功能:结束进程,不刷新缓存

参数数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    printf("hello");
    //exit(0);   //结束进程,刷新缓存
    _exit(0);    //结束进程,不刷新缓存
    while (1);

    //思考:exit和return的区别?
    
    return 0;
}

eixt结束的是当前整个进程,return结束的是当前函数。
获取进程号

pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号

exec函数族(了解)

在一个进程中执行另一个进程:
system(“clear”);
system(“ls -l”);
system函数所启动进程不会替换原本进程,但是exec函数族所启动进程会替换原本进程。
守护进程 Daemon
Linux以会话(session)、进程组的方式管理进程,每个进程属于一个进程组,也就是多个进程组成一个进程组。会话是一个或多个进程组的集合,通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话。终端关闭时,所有相关进程会被结束。但是守护进程却能突破这种限制,不受终端关闭的影响。
守护进程特点
守护进程是后台进程;
生命周期比较长,从系统开启时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
创建步骤
1) 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
2) 在子进程中创建新会话
让子进程成为会话组组长并且脱离终端:为了让子进程完全脱离终端;setsid()
3) 改变进程运行路径为根目录
原因: 进程运行的路径不能被删除或卸载;chdir(“/”)
函数说明:chdir() 将进程当前的工作目录改变成以参数路径所指的目录
4) 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
子进程继承了父进程的文件权限掩码,给该子进程使用文件带来一定的影响,因此把文件 权限掩码设置为0,可以增强该守护进程的灵活性。
5) 关闭文件描述符
原因:子进程继承了父进程的一些已经打开了的文件,这些被打开的文件可能永远不会被 守护进程访问,但它们一样占用系统资源,而且还可能导致所在的文件系统无法被卸载。
将不需要的文件关闭:close()

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

int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if(pid<0)
    {
        perror("fork err");
        return -1;
    }
    else if(pid == 0)
    {
        setsid();
        chdir("/");
        umask(0);
        for(int i=0;i<3;i++)  //关闭默认打开的文件描述符0 1 2 
            close(i);
        while(1);
    }
    else
    {
        exit(0);
    }
     
    return 0;
}

线程

什么是线程

概念
线程是一个轻量级的进程,为了提高系统的性能引入线程。
线程和进程都参与统一的调度。
在同一个进程中可以创建的多个线程, 共享进程资源。
(Linux里同样用task_struct来描述一个线程)
进程和线程的区别
相同点:都为操作系统提供了并发执行能力
不同点:
调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位
地址空间方面:同一个进程创建的多个线程共享该进程的资源;进程的地址空间相互独立
通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安全
线程资源
共享的资源:可执行的指令、静态数据、进程中打开的文件描述符、信号处理函数、当前工作目录、用户ID、用户组ID
私有的资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈(局部变量, 返回地址)、错误号 (errno)、信号掩码和优先级、执行状态和属性
练习: 通过父子进程完成对文件的拷贝(cp)

函数接口

创建线程:pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                                void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:       thread:线程标识
            attr:线程属性, NULL:代表设置默认属性
            start_routine:函数名:代表线程函数(自己写的)
            arg:用来给前面函数传参
返回值:成功:0
      失败:错误码
      
编译的时候需要加 -pthread 链接动态库

创建线程

#include <stdio.h>
#include <pthread.h>

//线程函数
void *handler_thread(void *arg)
{
    printf("in handler_thread\n");
    while (1)
        ; //让从线程不退出,进程状态l也就是多线程
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, handler_thread, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    printf("in main\n");
    while (1)
        ; //不能让整个进程结束
    return 0;
}

退出线程:pthread_exit

void  pthread_exit(void *value_ptr) 
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值

回收线程资源

int  pthread_join(pthread_t thread,  void **value_ptr) 
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象,线程ID
     value_ptr:指针*value_ptr指向线程返回的参数, 一般为NULL
返回值:成功 : 0
       失败:errno

int pthread_detach(pthread_t thread);
功能:让线程结束时自动回收线程资源,让线程和主线程分离,非阻塞函数
参数:thread:线程ID
非阻塞式的,例如主线程分离(detach)了线程T2,那么主线程不会阻塞在pthread_detach()pthread_detach()会直接返回,线程T2终止后会被操作系统自动回收资源。
#include <stdio.h>
#include <pthread.h>

//线程函数
void *handler_thread(void *arg)
{
    printf("in handler_thread\n");
    pthread_exit(NULL); //让从线程退出
    while (1)
        ;
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, handler_thread, NULL) != 0)
    {
        perror("create thread err");
        return -1;
    }
    printf("in main\n");

    //回收指定线程资源
    //pthread_join(tid,NULL);
    pthread_detach(tid);

    while (1)
        ; //不能让整个进程结束

    return 0;
}

同步

概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
(异步:异步则反之,并非一定需要一件事做完再做另一件事。)
同步机制
通过信号量实现线程间同步。
信号量:通过信号量实现同步操作;由信号量来决定线程是继续运行还是阻塞等待.
信号量代表某一类资源,其值表示系统中该资源的数量:
信号量的值>0,表示有资源可以用, 可以申请到资源,
信号量的值<=0, 表示没有资源可以通用, 无法申请到资源, 阻塞.
信号量还是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)

sem_init: 信号量初始化
sem_wait: 申请资源,P操作, 如果没有资源可以用, 阻塞,-1
sem_post: 释放资源,V操作, 非阻塞 +1

函数接口

int  sem_init(sem_t *sem,  int pshared,  unsigned int value)  
功能:初始化信号量   
参数:sem:初始化的信号量对象
    pshared:信号量共享的范围(0: 线程间使用   非0:1进程间使用)
    value:信号量初值
返回值:成功 0
       失败 -1

int  sem_wait(sem_t *sem)  
功能:申请资源  P操作 
参数:sem:信号量对象
返回值:成功 0
       失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
       
int  sem_post(sem_t *sem)   
功能:释放资源  V操作      
参数:sem:信号量对象
返回值:成功 0
      失败 -1
注:释放一次信号量的值加1,函数不阻塞
#include <semaphore.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>

sem_t sem;
char buf[32];

void *handler_thread(void *arg)
{
    while (1)
    {
        //申请资源
        sem_wait(&sem);
        if (strcmp(buf, "quit") == 0)
            break;
        printf("%s\n", buf);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    if (pthread_create(&tid, NULL, handler_thread, NULL) != 0)
    {
        perror("pthread create err");
        return -1;
    }

    //初始化信号量
    if (sem_init(&sem, 0, 0) != 0)
    {
        perror("sem init err");
        return -1;
    }

    while (1)
    {
        
        scanf("%s", buf);
        //释放资源
        sem_post(&sem);
        if (strcmp(buf, "quit") == 0)
            break;
    }

    pthread_join(tid, NULL);
    return 0;
}

互斥

概念
互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
临界资源:一次仅允许一个线所使用的资源
临界区:指的是一个访问共享资源的程序片段
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。

pthread_mutex_init
pthread_mutex_lock
pthread_mutex_unlock

函数接口

int  pthread_mutex_init(pthread_mutex_t  *mutex, pthread_mutexattr_t *attr)  
功能:初始化互斥锁  
参数:mutex:互斥锁
    attr:  互斥锁属性  //  NULL表示缺省属性
返回值:成功 0
      失败 -1

int  pthread_mutex_lock(pthread_mutex_t *mutex)   
功能:申请互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回

int  pthread_mutex_unlock(pthread_mutex_t *mutex)   
功能:释放互斥锁     
参数:mutex:互斥锁
返回值:成功 0
      失败 -1

int  pthread_mutex_destroy(pthread_mutex_t  *mutex)  
功能:销毁互斥锁     
参数:mutex:互斥锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
pthread_mutex_t lock;

void *handler_print(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&lock);
        for (int i = 0; i < 10; i++)
            printf("%d ", a[i]);
        printf("\n");
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
    return NULL;
}

void *handler_swap(void *arg)
{
    int t;
    while (1)
    {
        pthread_mutex_lock(&lock);
        for (int i = 0; i < 5; i++)
        {
            t = a[i];
            a[i] = a[9 - i];
            a[9 - i] = t;
        }
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t tid1;
    if (pthread_create(&tid1, NULL, handler_print, NULL) != 0)
    {
        perror("pthread print create err");
        return -1;
    }

    pthread_t tid2;
    if (pthread_create(&tid2, NULL, handler_swap, NULL) != 0)
    {
        perror("pthread swap create err");
        return -1;
    }

    //初始化互斥锁
    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        perror("init err");
        return -1;
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&lock);
    return 0;
}

死锁
是指两个或两个以上的进程或线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
死锁产生的四个必要条件
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

条件变量

条件变量用于在线程之间传递信号,以便某些线程可以等待某些条件发生。当某些条件发生时,条件变量会发出信号,使等待该条件的线程可以恢复执行。

一般和互斥锁搭配使用,实现同步机制:
pthread_cond_init(&cond,NULL); //初始化条件变量

使用前需要上锁:
pthread_mutex_lock(&lock); //上锁
判断条件
pthread_cond_wait(&cond, &lock); //阻塞等待条件产生,没有条件产生时阻塞,同时解锁,当条件产生时结束阻塞,再次上锁

pthread_cond_signal(&cond); //产生条件,不阻塞

pthread_cond_destroy(&cond); //销毁条件变量

注意: 必须保证让pthread_cond_wait先执行,pthread_cond_signal再产生条件

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

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

相关文章

做抖音小店如何选品?这几个技巧,精准“锁定”爆品!

哈喽~我是电商月月 做抖音小店最重要的就是选品&#xff0c;这点大家都知道 一个店铺商品选的好&#xff0c;顾客喜欢&#xff0c;质量完好&#xff0c;销量和售后都不用操心&#xff0c;和达人合作时&#xff0c;爆单的机会也就越高 那这种商品是什么样的&#xff0c;新手开…

基于ssm微信小程序的4S店客户管理系统

采用技术 基于ssm微信小程序的4S店客户管理系统的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringMVCMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 管理员端 管理员登录 管理员首页 用户管理 门店管理 …

RustGUI学习(iced)之小部件(一):如何使用按钮和文本标签部件

前言 本专栏是学习Rust的GUI库iced的合集&#xff0c;将介绍iced涉及的各个小部件分别介绍&#xff0c;最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个&#xff0c;目前处于发展中&#xff08;即版本可能会改变&#xff09;&#xff0c;本专栏基于版本0.12.1. 概述…

高效一键改写文章,智能伪原创工具轻松搞定

在信息爆炸的时代&#xff0c;想要高效率的一键改写文章却是很多创作者都想了解的方法。然而在人工智能技术发展的今天&#xff0c;智能伪原创工具的出现&#xff0c;也正是成了广大创作者用来一键改写文章的好方法&#xff0c;因为它的优势&#xff0c;可以为大家轻松完成改写…

光伏二次设备主要有哪些

光伏电站二次设备类型比较多&#xff0c;信息显示、数据安全、远动通信、电能质量、微机保护等都有不同设备相互配合完成&#xff0c;根据项目具体需求来选择&#xff0c;简单可以分为以下几种&#xff1a; 一、光伏二次设备保护屏&#xff1a; 1、光伏二次设备预制舱 二次设…

短视频矩阵系统源码====3年技术公司源头开发商交付

短视频矩阵系统#源头技术打磨 哈尔滨爆火带动了一波“北上热潮”&#xff0c;各地文旅坐不住了&#xff0c;兄弟们开“卷”&#xff01;这波互卷浪潮中&#xff0c;河南率先出圈。如今&#xff0c;河南文旅账号粉丝已经突破200w&#xff01; 01 矩阵打法&#xff0c;很难不火…

超越边界:如何ChatGPT 3.5、GPT-4、DALL·E 3和Midjourney共同重塑创意产业

KKAI&#xff08;kkai人工智能&#xff09;是一个整合了多种尖端人工智能技术的多功能助手平台&#xff0c;融合了OpenAI开发的ChatGPT3.5、GPT4.0以及DALLE 3&#xff0c;并包括了独立的图像生成AI—Midjourney。以下是这些技术的详细介绍&#xff1a; **ChatGPT3.5**&#xf…

Lab2: system calls

Using gdb Looking at the backtrace output, which function called syscall? 可以看到是trap.c中usertrap函数调用了syscall函数 What is the value of p->trapframe->a7 and what does that value represent? p->trapframe->a7的值为7&#xff0c;代表了函…

MATLAB 条件语句

MATLAB 条件语句 决策结构要求程序员应指定一个或多个要由程序评估或测试的条件&#xff0c;如果确定条件为真&#xff0c;则应指定要执行的一个或多个语句&#xff0c;如果条件为真&#xff0c;则可以选择要执行的其他语句。条件确定为假。 以下是大多数编程语言中常见的典型…

我们支持批量了!

当我们有很多文件要处理时&#xff0c;一个一个操作不仅费时费力&#xff0c;而且还容易漏掉某个文件。那么有没有更加简单的方法呢&#xff1f;可以试试批量功能&#xff01; 目前以下功能都支持批量操作 音频提取 视频格式转换 字幕生成 视频静音 图片格式转换 图片压缩 视频…

JavaScript —— APIs(四)

一、日期对象 1. 实例化 new 括号里面为空&#xff0c;得到当前时刻的时间 括号里面为指定日期&#xff0c;得到对应日期的时间 注意&#xff1a;若括号里面只有年月日&#xff0c;没有时间&#xff0c;则得到的结果就没有时间&#xff1b;括号里面指定时间是多少&#xff0c;…

身份证二要素核验介绍及使用方法

一、身份证二要素核验简介及重要性 身份证二要素核验是一种重要的身份验证技术&#xff0c;它在现代社会中发挥着至关重要的作用&#xff0c;特别是在涉及个人信息安全和隐私保护的领域。通过身份证二要素核验&#xff0c;我们可以有效地确认个人身份的真实性&#xff0c;从而…

爬虫学习笔记-数美验证

测试网址&#xff1a;智能验证码体验_图片验证码_数美科技数美科技智能验证码在线体验&#xff0c;智能识别风险用户级别&#xff0c;自行切换智能验证码难度及类型&#xff0c;提供滑动、拼图、点选、数字、动态等多种智能验证码服务&#xff0c;精准拦截机器行为。https://ww…

SOLIDWORKS Composer如何使用3D工具实现更真实的动画效果

当我们使用SOLIDWORKS composer创建动画时&#xff0c;往往会涉及到产品的安装与拆解&#xff0c;现实生活中我们在拆卸组装产品的时候&#xff0c;我们往往需要一些工具的协助&#xff0c;比如扳手、螺丝刀等等&#xff0c;那么我们如何在虚拟动画中也将这一过程以逼真的形式展…

新建云仓库

1.GitHub新建云仓库&#xff1a; LICENSE:开源许可证&#xff1b;README.md:仓库说明文件&#xff1b;开源项目&#xff1b;cocoaPodsName.podspec: CocoaPods项目的属性描述文件。 2.Coding新建云仓库&#xff1a; 备注&#xff1a; Coding新建项目&#xff1a;

STM32,复位和时钟控制

外部时钟 HSE 以后需要用到什么就这样直接拿去配就行了

左叶子之和(力扣404)

解题思路:用后序遍历找左孩子&#xff0c;需要注意的是左叶子需要通过其父节点来判断其是不是左叶子 具体代码&#xff1a; class Solution { public: int sumOfLeftLeaves(TreeNode * root) { if(rootNULL)return 0; if(root->leftNULL&&root->rightNULL)ret…

纳米尺度下的单粒子追踪,厦门大学方宁团队用 AI 奏响「细胞里的摇滚」

在微观世界里&#xff0c;每一个细胞都是一个繁忙的城市&#xff0c;而分子们则是这个城市中的居民。想象一下&#xff0c;如果我们能够追踪这些居民的每一个动作&#xff0c;或许便能够揭开生命奥秘的一角。这就是科学家们在活细胞中进行 3D 单粒子跟踪 (single particle trac…

lua整合redis

文章目录 lua基础只适合lua连接操作redis1.下载lua依赖2.导包,连接3.常用的命令1.set,get,push命令 2.自增管道命令命令集合4.使用redis操作lua1.实现秒杀功能synchronized关键字 分布式锁 lua 基础只适合 1.编译 -- 编译 luac a.lua -- 运行 lua a.lua2.命名规范 -- 多行注…

数字化转型成功的企业到底是什么样的?

数字化转型成功的企业通常具备以下特征&#xff1a; 1、以客户为中心的业务模式&#xff1a;成功的数字化转型企业将客户放在业务模式的核心位置&#xff0c;通过提供个性化的服务和产品来满足客户需求。这种以客户为中心的模式能够带来更好的客户体验和忠诚度。 2、强大的数…