linux--多线程(一)

news2024/10/2 20:31:55

文章目录

      • Linux线程的概念
        • 线程的优点
        • 线程的缺点
        • 线程异常
      • 线程的控制
        • 创建线程
        • 线程ID以及进程地址空间
        • 终止线程
        • 线程等待
        • 线程分离
        • 线程互斥
          • 进程线程间的互斥相关概念
          • 互斥量mutex
          • 有线程安全问题的售票系统
          • 查看ticket--部分的汇编代码
          • 互斥量的接口
          • 互斥量实现原理探究
        • 可重入和线程安全
          • 常见的线程不安全的情况
          • 常见的线程安全的情况
          • 常见不可重入的情况
          • 常见可重入的情况
          • 可重入与线程安全的联系与区别
        • 常见锁的概念
          • 死锁
          • 死锁的四个必要条件

Linux线程的概念

  • 在一个程序里的一个执行路线就叫做线程(thread),更准确的定义是:线程是”一个进程内部的控制序列“
  • 一切进程至少都有一个执行线程,线程在进程内部运行,本质是在进程地址空间内运行
  • 在Linux系统中,在CPU眼里,看到的PCB要比传统的进程更加轻量化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKOvTHlq-1677599958611)(D:\blogs\Linux中的执行流共享内存空间.png)]

线程的优点

  • 创建一个新线程的代价要比创建一个新进程要小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用:为了能在多处理系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用:为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点

性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

健壮性降低

编写多线程程序需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,也就是说线程 之间是缺乏保护的

缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也随即退出

线程的控制

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_“打头的。要使用这些函数库要通过引入头文件<pthread.h>,链接这些线程函数库时要使用编译器命令的”-lpthread"选项。

创建线程

/*
功能:创建一个新的线程
函数原型:
	int pthread_create(pthread_t* thread, const pthread_attr_t *arrt, void* (*call)(void*), void* arg);
	thread:返回线程ID
	attr:设置线程的属性,attr为NULL表示使用默认属性
	call:是一个函数指针,线程启动后要执行的函数
	arg:传给线程启动函数的参数
返回值:成功返回0,失败返回错误码
*/

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

void* call_back(void* arg)
{
    for(;;)
    {
        printf("我是线程:%d&& %s\n",pthread_self(),(char*)arg);
        sleep(1);
    }
}

int main()
{

    pthread_t tid;
    pthread_create(&tid,NULL,call_back,"hello word");
    while(1)
    {
        printf("我是父线程:%d, 创建了子线程:%d\n",pthread_self(),tid);
        sleep(1);
    }
    return 0;
}

创建线程的运行结果

在这里插入图片描述

线程ID以及进程地址空间

  • pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事

  • 前面说的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程

  • pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴,线程库的后续操作,就是根据该线程ID来操作线程的

  • NPTL线程库提供了pthread_self函数,可以获得线程自身的ID

  • pthread_t pthread_self(void);
    

pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址

在这里插入图片描述

终止线程

终止一个线程而不终止整个进程,有三种方法:

  1. 从线程函数return,这种方法对主线程不适用,从main函数return相当于调用exit

  2. 线程可以调用pthread_exit终止自己

    /*
    功能:终止线程
    原型
    */
    int pthread_exit(void* value_ptr);
    /*参数*/
    // value_ptr:value_ptr不要指向一个局部变量,它指向的数据将作为线程退出时的返回值,如果线程不需要返回任何数据,将value_ptr参数置为NULL即可
    // 无返回值,跟进程一样,线程借宿的时候无法返回到它的调用者
    
    // 举个例子
    #include <unistd.h>
    #include <pthread.h>
    #include <stdio.h>
    
    void* func(void* args)
    {
      printf("欢迎光临\n");
      sleep(3);
      pthread_exit((void*)"tuichu");
    }
    
    int main()
    {
    
      int ret = 0;
      void* str;
      pthread_t tid;
      ret = pthread_create(&tid,NULL,func,NULL);
      pthread_join(tid,&str);
      printf("%s\n",(char*)str);
      return 0;
    }
    

在这里插入图片描述

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

    /*功能取消一个线程*/
    //原型
    	void pthread_cancel(pthread_t tid);
    // 参数
    	tid:线程ID
    // 返回值:成功返回0,失败返回错误码
    // 举个例子
    #include <unistd.h>
    #include <pthread.h>
    #include <stdio.h>
             
    void* func(void* args)
    {
      while(1)
      {
        printf("我是线程%d\n",pthread_self()); 
        sleep(1);
      }
    }
             
    int main()
    {
             
      pthread_t tid1, tid2;
      pthread_create(&tid1,NULL,func,NULL);
      pthread_create(&tid2,NULL,func,NULL);
      pthread_detach(tid1);
      pthread_detach(tid2);
      sleep(3);
      pthread_cancel(tid1);
      printf("取消线程%d\n",tid1);
      sleep(2);
      printf("取消线程%d\n",tid2);
      pthread_cancel(tid2);
      return 0;
    }
             
    

在这里插入图片描述

线程等待

已经退出的线程,其空间没有被释放,任然在进程的地址空间内。此时创建新的线程是不会复用刚才退出线程的地址空间的

//功能:等待线程结束
//原型
	int pthread_join(pthread_t tid, void** value_ptr);
// 参数
/*
	tid:线程ID
	value_ptr:它指向一个指针,后者指向线程的返回值
	返回值:成功返回0,失败返回错误码
*/

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下

  1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_cancel异常终止,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELD
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给phtread_exit的参数
  4. 如果不考虑线程的终止状态,可以传NULL给value_ptr

线程分离

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄露
  • 如果不关心线程的返回值,join是一种负担,因为主线程会阻塞在join这一步,这个时候我们可以告诉系统,当线程退出时,自动释放线程资源
// 可以主线程分离子线程
int pthread_detach(pthread_t tid);
// 也可以子线程自己分离自己
int pthread_detach(pthread_self);
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run( void * arg )
{
	pthread_detach(pthread_self());
	printf("%s\n", (char*)arg);
	return NULL;
}
int main( void )
{
	pthread_t tid;
	if ( pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) {
		printf("create thread error\n");
		return 1;
	}
	int ret = 0;
	sleep(1);//很重要,要让线程先分离,再等待
	if ( pthread_join(tid, NULL ) == 0 ) {
		printf("pthread wait success\n");
		ret = 0;
	} else {
		printf("pthread wait failed\n");
		ret = 1;
	}
	return ret;
}

线程互斥

进程线程间的互斥相关概念
  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两种状态,要么完成,要么未完成
互斥量mutex
  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
  • 很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互
有线程安全问题的售票系统
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
// 定义100张票
int ticket = 100;

// 线程的回调函数
void* route(void* arg)
{
    char* id = (char*)arg;
    while(1)
    {
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--; // 剩余票数减一
        }else{
            break;
        }
    }
}

int main()
{
    // 创建4个线程模拟买票操作
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, "thread 1");
    pthread_create(&t2, NULL, route, "thread 2");
    pthread_create(&t3, NULL, route, "thread 3");
    pthread_create(&t4, NULL, route, "thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    
    return 0;
}

在这里插入图片描述

查看执行结果我们发现售卖的票为0的时候还在往外售出,这肯定是存在问题,那么是为什么会导致这样的情况出现呢?

  • if语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • --ticket操作本身就不是一个原子操作
查看ticket–部分的汇编代码

使用 objdump -d ticketing_system > test.objdump指令获取汇编代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NAH0hGOq-1677599958615)(D:\blogs\ticket–的汇编代码.png)]

可以看到--操作是由很多步构成的,并不是原子的

  • load:将共享变量ticket从内存加载到寄存器中
  • update:更新寄存器里面的值,执行-1操作
  • store:将新值,从寄存器写回共享变量ticket的内存地址

要解决以上问题,需要做到三点

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到以上三点,本质上就是需要一把锁,Linux上提供的这把锁叫互斥量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKlm0Ls3-1677599958615)(D:\blogs\互斥量.png)]

互斥量的接口

初始化互斥量

初始化互斥量有两种方法:

  • 方法1:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 方法2:动态分配
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr);
/*
参数:
	mutex:要初始化的互斥量
	attr:NULL
*/

销毁互斥量

销毁互斥量需要注意:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t* mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
返回值:成功返回0,失败返回错误号

调用pthread_lock时,可能回遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用回陷入阻塞,等待互斥量解锁

改进后的售票系统

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>

int ticket = 100;
// 定义互斥量
pthread_mutex_t mutex;
void *route(void *arg)
{
    char *id = (char *)arg;
    // 加锁
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
            // 解锁
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            // 如果没有票了也要释放锁
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}

int main()
{
    pthread_t t1, t2, t3, t4;

    // 初始化锁
    pthread_mutex_init(&mutex, NULL);

    pthread_create(&t1, NULL, route, "thread 1");
    pthread_create(&t2, NULL, route, "thread 2");
    pthread_create(&t3, NULL, route, "thread 3");
    pthread_create(&t4, NULL, route, "thread 4");

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
    // 销毁锁
    pthread_mutex_destroy(&mutex);
    return 0;
}
互斥量实现原理探究
  • 经过上面的例子,大家已经意识到单纯的++i或者i++都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或者exchange指令,该指令的作用是把寄存器喝内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理上的交换指令执行时另一个处理器的交换指令只能等待总线周期

把lock和unlock的伪代码改一下

lock:
	movb $0, %al 
    xchgb %al, mutex // 原子操作,直接将al的值和mutex交换
    if(al寄存器的内容 > 0) // 竞争到锁
    {
        return 0;
    }else // 没竞争到锁
        挂起等待;
	goto lock;
unlock:
	movb $1, mutex
    唤醒等待mutex的线程;
	return 0;

可重入和线程安全

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全问题。
  • 重入:同一个函数被不同的执行流调用,当一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入情况下,运行结果不会出现任何不同或者任何问题,则函数被称为可重入函数,否则,是不可重入函数。
常见的线程不安全的情况
  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数
常见的线程安全的情况
  • 每个线程对全局变量或者静态变量只有读取权限,而没有写入权限,一般来说这些线程是安全的
  • 类或接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见不可重入的情况
  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构
常见可重入的情况
  • 不使用全局变量或静态变量
  • 不使用用mallocnew开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都由函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
可重入与线程安全的联系与区别

联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数一定是线程安全的
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

常见锁的概念

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于一种永久等待状态

死锁的四个必要条件
  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

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

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

相关文章

三重积分为何不能直接带入积分区域?搞懂这些,重积分基本可以了

积分的积分区域及被积表达式 重点&#xff1a;积分的结果均为数值&#xff0c;仅与被积表达式和积分区间有关&#xff01;&#xff01;&#xff01; 1.如何一下子区分一重积分&#xff0c;二重积分&#xff0c;三重积分&#xff1f; 看积分区间和被积表达式&#xff1a; 一重…

React教程详解一(props、state、refs、生命周期)

文章略长&#xff0c;耐心读完&#xff0c;受益匪浅哦~ 目录 前言 简介 JSX 面向组件编程 state props refs 组件生命周期 前言 简介 React框架由Facebook开发&#xff0c;和Vue框架一样&#xff0c;都是用于构建用户界面的JavaScript库&#xff1b; 它有如下三个特…

PHP - ChatGpt 学习 仅供参考

由于最近ChatGpt 大火&#xff0c;但是门槛来说是对于大家最头疼的环节&#xff0c; 由此ChatGpt 有一个API 可以仅供大伙对接 让我来说下资质&#xff1a; 1&#xff1a;首先要搞得到一个 ChatGpt 的账户&#xff0c; 会获得一个KEY&#xff0c;该key为访问API核心&#xff0…

jenkins漏洞集合

目录 CVE-2015-8103 反序列化远程代码执行 CVE-2016-0788 Jenkins CI和LTS 远程代码执行漏洞 CVE-2016-0792 低权限用户命令执行 CVE-2016-9299 代码执行 CVE-2017-1000353 Jenkins-CI 远程代码执行 CVE-2018-1000110 用户枚举 CVE-2018-1000861 远程命令执行 CVE-2018…

ChatGPT文章自动发布WordPress

WordPress可以用ChatGPT发文章吗&#xff1f;答案是肯定的&#xff0c;ChatGPT官方有提供api接口&#xff0c;多以目前有很多的SEO工具具有自动文章生成自动发布的功能&#xff0c;使用SEO工具&#xff0c;我们可以通过疑问词和关键词进行文章生成&#xff0c;并定时发布到我们…

软测入门(三)Selenium(Web自动化测试基础)

Selenium&#xff08;Web端自动测试&#xff09; Selenium是一个用于Web应用程序测试的工具&#xff1a;中文是硒 开源跨平台&#xff1a;linux、windows、mac核心&#xff1a;可以在多个浏览器上进行自动化测试多语言 Selenium WebDriver控制原理 Selenium Client Library…

Linux基础——连接Xshell7

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

复位理论基础

先收集资料&#xff0c;了解当前常用的基础理论和实现方式 复位 初始化微控制器内部电路 将所有寄存器恢复成默认值确认MCU的工作模式禁止全局中断关闭外设将IO设置为高阻输入状态等待时钟趋于稳定从固定地址取得复位向量并开始执行 造成复位的原因 有多种引起复位的因素&…

客户关系管理挑战:如何保持客户满意度并提高业绩?

当今&#xff0c;各行业市场竞争愈发激烈&#xff0c;对于保持客户满意度并提高业绩是每个企业都面临的挑战。而客户关系管理则是实现这一目标的关键&#xff0c;因为它涉及到与客户的互动和沟通&#xff0c;以及企业提供优质的产品和服务。在本文中&#xff0c;我们将探讨客户…

使用Geth搭建多节点私有链

使用Geth搭建多节点私有链 步骤 1.编辑初始化配置文件genesis.json {"config": {"chainId": 6668,"homesteadBlock": 0,"eip150Block": 0,"eip150Hash": "0x000000000000000000000000000000000000000000000000000000…

JDK下载安装与环境

&#x1f972; &#x1f978; &#x1f90c; &#x1fac0; &#x1fac1; &#x1f977; &#x1f43b;‍❄️&#x1f9a4; &#x1fab6; &#x1f9ad; &#x1fab2; &#x1fab3; &#x1fab0; &#x1fab1; &#x1fab4; &#x1fad0; &#x1fad2; &#x1fad1;…

回暖!“数”说城市烟火气背后

“人间烟火气&#xff0c;最抚凡人心”。在全国各地政策支持以及企业的积极生产运营下&#xff0c;经济、社会、生活各领域正加速回暖&#xff0c;“烟火气”在城市中升腾&#xff0c;信心和希望正在每个人心中燃起。 发展新阶段&#xff0c;高效统筹经济发展和公共安全&#…

JavaEE进阶第六课:SpringBoot配置文件

上篇文章介绍了SpringBoot的创建和使用&#xff0c;这篇文章我们将会介绍SpringBoot配置文件 目录1.配置文件的作用2.配置文件的格式2.1 .properties语法2.1.1.properties的缺点2.2 .yml语法2.2.1优点分析2.2.2配置与读取对象2.2.3配置与读取集合2.2.4补充说明3.设置不同环境的…

陪诊小程序开发|陪诊小程序有哪些功能

陪诊小程序主要就是一款为服务一些无法单独或者很难单独完成就诊流程的人&#xff0c;提供线上下单寻求专业人员陪同就医的一款小程序&#xff0c;陪诊员线上接单&#xff0c;线下为患者提供专业的医疗陪同就医服务。相对于陪诊小程序开发多少钱很多人更想知道陪诊小程序有哪些…

动态规划:leetcode 139.单词拆分、多重背包问题

leetcode 139.单词拆分leetcode 139.单词拆分给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。说明&#xff1a;拆分时可以重复使用字典中的单词。你可以假设字典中没有重复的单词。示例 1&…

数据库设计规范1

数据库设计规范1 1.所有的表都必须有几个公共字段 这里建有删除标识&#xff0c;代表着我们所有的删除都是软删除。所以我们的删除操作其实是updata 2.id等数字类型的字段&#xff0c;不能知识bigint&#xff0c;应该是bigint unsigned 3.varchar默认长度为32&#xff0c;不…

MTK平台开发入门到精通(Thermal篇)热管理介绍

文章目录 一、热管理组成二、Linux Thermal Framework2.1、thermal_zone 节点2.2、cooling_device 节点三、Thermal zones沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍MTK平台的热管理机制,热管理机制是为了防止模组在高温下工作导致硬件损坏而存在的…

有趣的 Kotlin 0x10:操作符 ..<

操作符 …< ..< 操作符是 Kotlin 在 1.7.20 版本中引入的不包含尾部元素的左闭右开区间操作符。之前我们使用的比较多的操作符可能是 .. 和 until&#xff0c;两者均表示区间&#xff0c;前者是闭区间&#xff0c;后者则表示不包含末端元素的左闭右开区间。 OptIn(Expe…

C++ STL学习之【string类的模拟实现】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 The key is to keep company only with people who uplift you, whose presence calls forth your best. 关键是只与那些提升你的人在一起&#xff0c…

机器学习与目标检测作业(数组相加:形状需要满足哪些条件)

机器学习与目标检测&#xff08;数组相加:形状需要满足哪些条件&#xff09;机器学习与目标检测&#xff08;数组相加:形状需要满足哪些条件&#xff09;一、形状相同1.1、形状相同示例程序二、符合广播机制2.1、符合广播机制的描述2.2、符合广播机制的示例程序机器学习与目标检…