【Linux】线程安全

      • 线程互斥
        • 互斥相关背景概念
      • 互斥量mutex
      • 互斥量接口
        • 初始化互斥量函数
        • 销毁互斥量
        • 互斥量加锁
        • 互斥量解锁
        • 代码模拟
      • 互斥量实现的逻辑
      • 常见锁的概念
        • 死锁
        • 什么叫做阻塞?
        • 产生死锁的四个必要条件
        • 如何避免死锁
      • Linux线程同步
        • 同步概念与竞态条件
        • 条件变量
        • 条件变量函数
          • 代码练习
        • 条件变量使用规范

线程互斥

互斥相关背景概念
  • 临界资源:多线程执行流共享的资源叫做临界资源。
  • 临界区:每个线程内部,访问临界资源的代码区叫做临界区。
  • 互斥:任何时刻,互斥保证只有一个执行流进去临界区进行访问临界资源,通常对临界资源起到了保护作用。
  • 原子性:在Linux中,原子性指的是一个操作的不可分割性。具体来说,原子性是指一个操作在执行期间不会被其他操作中断的特性。在并发编程中,原子性非常重要,它确保多个线程或进程同时访问同一资源时的数据一致性;该操作只有两态,要么完成,要么未完成。

模拟实现临界区和临界资源

其实在多线程中,几乎我们访问到的临界区和临界资源较多,所以我们通过多线程就可以很简单的构造这么一个环境。如下代码:

int count = 0;
void* func(void* args)
{while(1){count++;sleep(1);}pthread_exit((void*)6);
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,func,nullptr);while(1){printf("我和多线程访问的是同一个全局变量count:%d\n",count);sleep(1);}pthread_join(tid,nullptr);return 0;
}

在这里插入图片描述
此时我们相当如实现了主线程和新线程之间的通信,其中全局变量const称之为临界资源,因为他被多个执行流共享,而主线程中的printf和新线程中的const++称之为临界区,因为这些代码对临界资源进行了访问。

互斥和原子性

当多个线程同时多一个临界资源进行访问修改的时候,那么此时就可能会导致不一致的问题,解决该问题的方案叫做互斥,互斥的作用就是保证任何时候只有一个执行流通过临界区进行对临界资源访问。

我们可以自定义实现一个抢票系统来验证的观察以下

int tickets = 1000;//总票数
void* func(void* args)
{const char* name = (char*)args;printf("begin:%s\n",name);while (1){if (tickets > 0){//以微妙为单位usleep(2000);//1秒 = 1000毫秒 ,1毫秒 = 1000微秒printf("[%s] get a ticket, left: %d\n", name, --tickets);}else{break;}}printf("%s quit!\n", name);pthread_exit((void*)0);
}int main()
{pthread_t tid[4];//char str[128];for(int i = 0;i < 4;i++){//sprintf(str,"thread: %d",i);char* str=new char[128];memset(str,0,128);snprintf(str,128,"thread: %d",i+1);printf("name:%s\n",str);//这一块首先我们是将str的首地址传入到func中,虽然说线程栈是私有的,但是每个都指向str数组本身,所以最后一个线程name覆盖pthread_create(tid + i, NULL, func, (void*)str);}for(int i = 0;i < 4;++i){pthread_join(tid[i],NULL);}printf("回收线程成功\n");return 0;
}

在这里插入图片描述

我们发现,在末尾结束的时候,票是已经成了负数了,这就是多个线程抢占临界资源导致的结果,出现这个结果的原因是:

  • 主要原因还是usleep,当我们等待的时候,其他进程已经拿上进行抢票了,当我们的usleep等待成功后在抢票时,这时如果没有票数了在抢就是负数。
  • --tickte操作本身不是一个原子操作

可以通过互斥来解决上述问题

互斥量mutex

要解决上述抢票系统并发的情况我们需要做到以下的方式:

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

我们通俗的将这种方式称作为上锁。
在这里插入图片描述

互斥量接口

初始化互斥量函数
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数说明:

  • mutex:需要初始化的互斥量
  • attr:初始化量的属性,一般我们设置为NULL即可。

返回值说明:

  • 互斥量初始化成功返回0,失败返回错误码。

调用pthread_mutex_init函数进行初始化互斥量我们叫做动态分配,一般我们用于局部的临界资源。

当我们遇到全局的临界资源时,我们一般也可以使用静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
销毁互斥量

销毁互斥量的函数为:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要销毁的互斥量。

返回值说明:

  • 互斥量销毁成功返回0,失败返回错误码。

销毁互斥量需要注意以下几点:

  • 当我们使用静态分配时,不需要进行销毁。(PTHREAD_MUTEX_INITIALIZER)
  • 不可以销毁一个已经加了锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
互斥量加锁

互斥量加锁的函数如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要加锁的互斥量

返回值说明:

  • 互斥量加锁成功后返回0,失败后返回错误码。

当调用互斥锁(pthread_mutex_lock)时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时对互斥量进行上锁,但是没有竞争到互斥量,那么此时的pthread_mutex_lock就会陷入阻塞(执行流被挂起),等待互斥量解锁。
互斥量解锁

互斥量解锁函数

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要解锁的互斥量。

返回值说明:

  • 互斥量解锁成功后返回0,解锁失败后返回失败错误码
代码模拟

接下来,我们来整体模拟一下以上所有的接口,就通过抢票系统来进行

int tickets = 1000;//总票数
//pthread_mutex_t mutex;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态锁
void* func(void* args)
{const char* name = (char*)args;while (1){pthread_mutex_lock(&mutex);if (tickets > 0){usleep(20);printf("[%s] get a ticket, left: %d\n", name, --tickets);pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}usleep(1000); //充当抢完一张票,后续动作}printf("%s quit!\n", name);pthread_exit((void*)0);
}int main()
{//初始化锁//pthread_mutex_init(&mutex,NULL);//动态锁pthread_t tid[4];//char str[128];for(int i = 0;i < 4;i++){//sprintf(str,"thread: %d",i);char* str = new char[128];memset(str,0,128);snprintf(str,128,"thread: %d",i+1);//这一块首先我们是将str的首地址传入到func中,虽然说线程栈是私有的,但是每个都指向str数组本身,所以最后一个线程name覆盖pthread_create(tid + i, NULL, func, (void*)str);}for(int i = 0;i < 4;++i){pthread_join(tid[i],NULL);}printf("回收线程成功\n");//销毁锁//pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述
注意:

  • 在大部分情况下,加锁本身都是有损于性能的事,它让多执行流由并行执行变为了串行执行,这几乎是不可避免的。
  • 我们应该在合适的位置进行加锁和解锁,这样能尽可能减少加锁带来的性能开销成本。
  • 进行临界资源的保护,是所有执行流都应该遵守的标准,这时程序员在编码时需要注意的。

互斥量实现的逻辑

加锁后是怎么实现原子性的

当我们使用互斥量后,其他线程看待我们的态度就变为了这个线程是否上锁和未上锁。

我们通过一个图来理解一下,当线程1进行上锁时来了好多线程,这时他们共同抢占一个资源,当这个资源处于别线程1进行上锁的状态时,其他线程是不可以进入的,至于线程 1 在里面干啥都可以,只要它一直处于上锁状态就行。

在这里插入图片描述
此时对于线程2、3、4而言,它们就认为线程1的整个操作过程是原子的。

临界区内可以进行线程切换吗?

临界区内的线程完全可能进行线程切换,但即便该线程被切走,其他线程也无法进入临界区进行资源访问,因为此时该线程是拿着锁被切走的,锁没有被释放也就意味着其他线程无法申请到锁,也就无法进入临界区进行资源访问了。

其他想进入该临界区进行资源访问的线程,必须等该线程执行完临界区的代码并释放锁之后,才能申请锁,申请到锁之后才能进入临界区。

锁是否需要保护?

我们说被多个执行流共享的资源叫做临界资源,访问临界资源的代码叫做临界区。所有的线程在进入临界区之前都必须竞争式的申请锁,因此锁也是被多个执行流共享的资源,也就是说锁本身就是临界资源。

既然锁是临界资源,那么锁就必须被保护起来,但锁本身就是用来保护临界资源的,那锁又由谁来保护的呢?

锁实际是一个原子性的,我们只需要保证申请锁的过程是原子的就行,它可以自己保护自己

为什么说申请锁的操作是原子的

下面我们来看看lock和unlock的伪代码:
在这里插入图片描述
我们可以认为mutex的初始值为1,al是计算机中的一个寄存器,当线程申请锁时,需要执行以下步骤:

  1. 先将al寄存器中的值清0。该动作可以被多个线程同时执行,因为每个线程都有自己的一组寄存器(上下文信息),执行该动作本质上是将自己的al寄存器清0。
  2. 然后交换al寄存器和mutex中的值。xchgb是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换。
  3. 最后判断al寄存器中的值是否大于0。若大于0则申请锁成功,此时就可以进入临界区访问对应的临界资源;否则申请锁失败需要被挂起等待,直到锁被释放后再次竞争申请锁。

例如,此时内存中mutex的值为1,线程申请锁时先将al寄存器中的值清0,然后将al寄存器中的值与内存中mutex的值进行交换。
在这里插入图片描述
交换完成后检测该线程的al寄存器中的值为1,则该线程申请锁成功,可以进入临界区对临界资源进行访问。

而此后的线程若是再申请锁,与内存中的mutex交换得到的值就是0了,此时该线程申请锁失败,需要被挂起等待,直到锁被释放后再次竞争申请锁。

在这里插入图片描述
当线程释放锁时,需要执行以下步骤:

  1. 将内存中的mutex置回1。使得下一个申请锁的线程在执行交换指令后能够得到1,形象地说就是“将锁的钥匙放回去”。
  2. 唤醒等待Mutex的线程。唤醒这些因为申请锁失败而被挂起的线程,让它们继续竞争申请锁。

注意:

  • 在申请锁时本质上就是哪一个线程先执行了交换指令,那么该线程就申请锁成功,因为此时该线程的al寄存器中的值就是1了。而交换指令就只是一条汇编指令,一个线程要么执行了交换指令,要么没有执行交换指令,所以申请锁的过程是原子的。
  • 在线程释放锁时没有将当前线程al寄存器中的值清0,这不会造成影响,因为每次线程在申请锁时都会先将自己al寄存器中的值清0,再执行交换指令。
  • CPU内的寄存器不是被所有的线程共享的,每个线程都有自己的一组寄存器,但内存中的数据是各个线程共享的。申请锁实际就是,把内存中的mutex通过交换指令,原子性的交换到自己的al寄存器中。

常见锁的概念

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。
在这里插入图片描述
例如上图所示:如果筷子的数量有限,这时,如果每个人手上分别拿一个筷子,谁也不让谁,此时就产生了死锁。

单执行流可能产生死锁码?

单执行流也有可能产生死锁,如果某一执行流连续申请了两次锁,那么此时该执行流就会被挂起。因为该执行流第一次申请锁的时候是申请成功的,但第二次申请锁时因为该锁已经被申请过了,于是申请失败导致被挂起直到该锁被释放时才会被唤醒,但是这个锁本来就在自己手上,自己现在处于被挂起的状态根本没有机会释放锁,所以该执行流将永远不会被唤醒,此时该执行流也就处于一种死锁的状态。

#include <stdio.h>
#include <pthread.h>pthread_mutex_t mutex;
void* Routine(void* arg)
{pthread_mutex_lock(&mutex);pthread_mutex_lock(&mutex);pthread_exit((void*)0);
}
int main()
{pthread_t tid;pthread_mutex_init(&mutex, NULL);pthread_create(&tid, NULL, Routine, NULL);pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);return 0;
}

此时程序一直处于挂起的状态
在这里插入图片描述
用ps命令查看该进程时可以看到,该进程当前的状态是Sl+,其中的l实际上就是lock的意思,表示该进程当前处于一种死锁的状态。

什么叫做阻塞?

进程运行时被CPU调度,换句话说进程在调度时是需要用到CPU资源的,每个CPU都有一个运行等待队列,CPU在运行时就是从该队列中获取进程进行调度的。

在这里插入图片描述
在运行等待队列中的进程本质上就是在等待CPU资源,只是因为锁被申请后,所有在申请同一把锁的资源全部被放到了等待队列中,比如锁的资源、磁盘的资源、网卡的资源等等,它们都有各自对应的资源等待队列。
在这里插入图片描述

例如,当某一个进程在被CPU调度时,该进程需要用到锁的资源,但是此时锁的资源正在被其他进程使用:

  • 那么此时该进程的状态就会由R状态变为某种阻塞状态,比如S状态。并且该进程会被移出运行等待队列,被链接到等待锁的资源的资源等待队列,而CPU则继续调度运行等待队列中的下一个进程。
  • 此后若还有进程需要用到这一个锁的资源,那么这些进程也都会被移出运行等待队列,依次链接到这个锁的资源等待队列当中。
  • 直到使用锁的进程已经使用完毕,也就是锁的资源已经就绪,此时就会从锁的资源等待队列中唤醒一个进程,将该进程的状态由S状态改为R状态,并将其重新链接到运行等待队列,等到CPU再次调度该进程时,该进程就可以使用到锁的资源了。

总结一下:

  • 站在系统的角度,进程等待某种资源就是将当前进程放入了进程的等待队列中,这种情况可以称之为当前进程被挂起等待了。
  • 站在用户的角度,当某个进程等待某种资源时,用户认为是进程卡的不动了,其实我们称之为应用阻塞了。
  • 这里所说的资源可以是硬件资源也可以是软件资源,锁本质就是一种软件资源,当我们申请锁时,锁当前可能并没有就绪,可能正在被其他线程所占用,此时当其他线程再来申请锁时,就会被放到这个锁的资源等待队列当中。
产生死锁的四个必要条件
  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求和保持条件:一个执行流因请求锁被阻塞,而另一个已获得锁的一直不放。
  • 不剥夺条件:一个执行流在获得锁的时候,不可以强行剥夺。
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

需要注意的是:如果要产生死锁必须这四个条件同时满足才能产生死锁。

如何避免死锁
  • 破坏产生锁的四个必要条件之一
  • 加锁顺序一致
  • 避免未释放锁的场景
  • 资源一次性分配

Linux线程同步

同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效的避免饥饿问题,这种做法就叫同步。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞太条件。

  • 饥饿问题:首先我们要知道,单纯的只是加锁也会存在某些问题。当我们给一个竞争力较强的一个线程上锁后,当他申请锁又释放锁后,由于它的竞争力强,就会再次申请到锁,这样重复操作,就会导致其他的线程出现饥饿问题。
  • 单纯的加锁是没有错的,它能够保证在同一时间只有一个线程进入临界区,但它没有高效的让每一个线程使用这份临界资源。
  • 现在我们加一个条件,就是当一个线程释放锁后,需要重新排队,排到等待队列的最后。
  • 增加这个规则之后,下一个获取到锁的资源的线程就一定是在资源等待队列首部的线程,如果有十个线程,此时我们就能够让这十个线程按照某种次序进行临界资源的访问。

例如:食堂打饭排队规则。

条件变量

条件变量是利用线程共享的全局变量进行同步的一种机制,条件变量用来描述一个线程资源是否处于就绪的状态。(数据化描述)

条件变量主要有两个动作:

  • 一个线程等待的条件变量成立而被挂起
  • 另一个线程条件成立后唤醒等待的线程

条件变量通常和互斥锁一起使用。

条件变量函数

初始化条件变量

初始化条件变量的函数叫做pthread_cond_init,该函数的函数原型如下:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

参数说明:

  • cond:需要初始化的条件变量
  • attr:初始化条件变量的属性,一般设置为NULL即可。

返回值说明:

  • 条件变量初始化成功返回0,失败返回错误码。

调用pthread_cond_init函数初始化条件变量叫做动态分配,除此之外,我们还可以用下面这种方式初始化条件变量,该方式叫做静态分配:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量

销毁条件变量的函数叫做pthread_cond_destroy,该函数的函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond:需要销毁的条件变量

返回值说明:

  • 条件变量销毁成功返回0,失败返回错误码。

需要注意的是,当我们使用的是静态分配时,条件初始化变量不可以销毁。

等待条件变量满足

等待条件变量满足的函数叫做pthread_cond_wait,该函数的函数原型如下:

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

参数说明:

  • cond:需要等待的条件变量
  • mutex:当前线程所处临界区对应的互斥锁

返回值说明:

  • 函数调用成功返回0,失败返回错误码。

唤醒等待

唤醒等待的函数有以下两个:

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

区别:

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程。

参数说明:

  • cond:唤醒在cond条件变量下等待的线程。

返回值说明:

  • 函数调用成功返回0,失败返回错误码。
代码练习
#include <iostream>
#include <unistd.h>
using namespace std;
const int nums = 5;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void *active(void *args)
{string name = static_cast<const char *>(args);while (1){pthread_mutex_lock(&mutex);// pthread_cond_wait,调用的时候,会自动释放锁。//起到阻塞作用pthread_cond_wait(&cond, &mutex);cout << name << "    活动" << endl;pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t tids[nums];for (int i = 0; i < nums; ++i){char *name = new char[32];snprintf(name, 32, "thread -- %d", i + 1);pthread_create(tids + i, nullptr, active, name);}cout << "开始等待" << endl;sleep(3);cout << "等待结束" << endl;while(1){cout << "唤醒等待队列的第一个线程" << endl;pthread_cond_signal(&cond);sleep(3);}for(int i = 0;i < nums;i++){pthread_join(tids[i],nullptr);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

在这里插入图片描述
我们可以从图中看出,我们的等待条件变量满足的作用就是起到了阻塞作用和有序。

总结一下

  • 等待的时候往往是在临界区内等待的,当该线程进入等待的时候,互斥锁会自动释放,而当该线程被唤醒时,又会自动获得对应的互斥锁。
  • 条件变量需要配合互斥锁使用,其中条件变量是用来完成同步的,而互斥锁是用来完成互斥的。
  • pthread_cond_wait函数有两个功能,一就是让线程在特定的条件变量下等待,二就是让线程释放对应的互斥锁。
条件变量使用规范

等待条件变量的代码

pthread_mutex_lock(&mutex);
while (条件为假)pthread_cond_wait(&cond, &mutex);
修改条件
pthread_mutex_unlock(&mutex);

切记先上锁,后等待

唤醒等待线程的代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

pytest之parametrize()实现数据驱动

第一个参数是字符串&#xff0c;多个参数中间用逗号隔开 第二个参数是list,多组数据用元组类型;传三个或更多参数也是这样传。list的每个元素都是一个元组&#xff0c;元组里的每个元素和按参数顺序一一对应 传一个参数 pytest.mark.parametrize(‘参数名’&#xff0c;list)…

【Spring Boot】拦截器学习笔记

一、普通拦截器 1&#xff0c;新建类MyWebConfig实现WebMvcConfigurer&#xff0c;实现addInterceptors方法 Overridepublic void addInterceptors(InterceptorRegistry registry) {registry// 不拦截哪些请求.excludePathPatterns("/login")// 拦截哪些请求.addPat…

Kubernetes 上的数据已跨越鸿沟:在 GKE 上运行有状态应用程序的案例

Kubernetes 是当今云原生开发的事实上的标准。长期以来&#xff0c;Kubernetes 主要与无状态应用程序相关&#xff0c;例如 Web 和批处理应用程序。然而&#xff0c;与大多数事物一样&#xff0c;Kubernetes 也在不断发展。如今&#xff0c;我们看到 Kubernetes 上有状态应用程…

在docker中删除none镜像

在构建过Docker镜像的电脑上查看本地镜像列表&#xff0c;有可能看到下图红框中的镜像&#xff0c;在列表中展示为:&#xff1a; 这种镜像在Docker官方文档中被称作dangling images&#xff0c;指的是没有标签并且没有被容器使用的镜像。 官方解释 来自官方的解释如下图红框所…

Springboot配置文件 - 多环境开发、自定义配置文件、多环境开发控制

文章目录 配置文件一、Yaml 文件1.1 修改banner1.2 日志1.3 端口1.4 属性提示消失解决方案 二、Properties 文件三、配置高级3.1 临时属性3.2 临时属性&#xff08;开发环境&#xff09;3.3 配置文件四级分类3.3.1 原始配置文件&#xff08;四级&#xff09;3.3.2 config目录下…

Rust vs C++ 深度比较

Rust由于其强大的安全性受到大量关注&#xff0c;被认为C在系统编程领域最强大的挑战者。本文从语言、框架等方面比较了两者的优缺点。原文: Rust vs C: An in-depth language comparison Rust和C的比较是开发人员最近的热门话题&#xff0c;两者之间有许多相似之处&#xff0c…

少数人的晚餐-补充

与此相关的四篇博客&#xff1a; 坦然~佛系~_坦然 佛系 zhangrelay-CSDN博客 少数人的晚餐_zhangrelay的博客-CSDN博客 ROS1/2机器人课程的价值和规模-CSDN博客 从2050回顾2020&#xff0c;职业规划与技术路径&#xff08;节选&#xff09;补充-CSDN博客 回顾 少数人的晚餐…

手把手教你制作精美的新店开业微传单

如果你准备开设一家新店&#xff0c;那么制作一份具有吸引力的微传单是宣传店铺的重要手段之一。下面&#xff0c;我们将通过乔拓云平台&#xff0c;手把手教你制作一份有吸引力的新店开业微传单。 1. 注册并登录乔拓云账号 首先&#xff0c;你需要在乔拓云官方网站注册一个账号…

加速企业AI实施:成功策略和效率方法

文章目录 写在前面面临的挑战MlOps简介好书推荐 写作末尾 写在前面 作为计算机科学领域的一个关键分支&#xff0c;机器学习在当今人工智能领域中占据着至关重要的地位&#xff0c;广受瞩目。机器学习通过深入分析大规模数据并总结其中的规律&#xff0c;为我们提供了解决许多…

机器学习---BP算法

1. 多级网络 层号确定层的高低&#xff1a;层号较小者&#xff0c;层次较低&#xff0c;层号较大者&#xff0c;层次较高。 输入层&#xff1a;被记作第0层。该层负责接收来自网络外部的信息。 第j层&#xff1a;第j-1层的直接后继层&#xff08;j>0&#xff09;&#xff…

使用Kalibr工具线对相机+IMU离线标定

传感器标定的准确后面做算法才会更准确&#xff0c;所以对Kalibr进行学习。 一、Kalibr编译 1、下载kalibr包 GitHub下载地址 2、 解压后放到/catkin_ws/src文件夹下 重新命令文件夹为kalibr 3、 安装依赖库 sudo apt-get install python-setuptools python-rosinstall…

css实现渐变电量效果柱状图

我们通常的做法就是用echarts来实现 比如 echarts象形柱图实现电量效果柱状图 接着我们实现进阶版&#xff0c;增加渐变效果 echarts分割柱形图实现渐变电量效果柱状图 接着是又在渐变的基础上&#xff0c;增加了背景色块的填充 echarts实现渐变电量效果柱状图 其实思路是一…

深度学习(1)---卷积神经网络

文章目录 一、发展历史1.1 CNN简要说明1.2 猫的视觉实验1.3 新认知机1.4 LeNet-51.5 AlexNet 二、卷积层2.1 图像识别特点2.2 卷积运算2.3 卷积核2.4 填充和步长2.5 卷积计算公式2.6 多通道卷积 三、池化层 一、发展历史 1.1 CNN简要说明 1. 卷积神经网络&#xff08;Convolut…

Spring源码分析 事务 实现原理

文章目录 什么是事务Spring事务管理Spring事务实现原理事务管理器事务定义事务的开启事务核心方法业务代码使用事务TransactionInterceptor 什么是事务 一般所指的事务是数据库事务&#xff0c;是指一批不可分割的数据库操作序列&#xff0c;也是数据库并发控制的基本单位。其…

26591-2011 粮油机械 糙米精选机

声明 本文是学习GB-T 26591-2011 粮油机械 糙米精选机. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了糙米精选机的有关术语和定义、工作原理、型号及基本参数、技术要求、试验方法、检 验规则、标志、包装、运输和储存要求。 …

常见列表字典排序

一、列表排序 demoList [1, 3, 2, 4, 9 ,7]res sorted(demoList) # 默认升序# 降序 # res sorted(demoList, reverseTrue)print(res)二、字典排序 demoDict {"篮球": 5, "排球": 9, "网球": 6, "足球": 3}# sorted排序 res so…

Spring面试题21:说一说Spring的@Required注解和@Qualifier注解

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的@Required注解 @Required ,用于标记在注入的属性上。它表示被注解的属性在配置 Bean 的时候是必需的,如果没有正确配置,则会抛出…

如何使用 API 接口获取商品数据,从申请 API 接口、使用 API 接口到实际应用,一一讲解

在当今的数字化时代&#xff0c;应用程序接口&#xff08;API&#xff09;已经成为数据获取的重要通道。API 接口使得不同的应用程序能够方便地进行数据交换&#xff0c;从而促进了信息的广泛传播和利用。在众多的数据源中&#xff0c;商品数据是一个非常重要的领域&#xff0c…

OS 模拟进程状态转换

下面的这个博主写的很好 但是他给的代码print部分和语言风格python三识别不了 这个特别感谢辰同学帮我调好了代码 我放在主页上了 估计过两天就可以通过了 《操作系统导论》实验一&#xff1a;模拟进程状态转换_process-run.py-CSDN博客 这个补充一下他没有的&#xff1a;OS…

R语言随机波动模型SV:马尔可夫蒙特卡罗法MCMC、正则化广义矩估计和准最大似然估计上证指数收益时间序列...

全文链接&#xff1a;http://tecdat.cn/?p31162 最近我们被客户要求撰写关于SV模型的研究报告&#xff0c;包括一些图形和统计输出&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 相关视频 本文做SV模型&#xff0c;选取马尔可夫蒙特卡罗法(MCMC)、正则化广…