【Linux】线程周边002之线程安全

news2024/11/20 0:46:25

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.Linux线程互斥

1.1互斥量的接口

1.1.1初始化互斥量

 1.1.2销毁动态分配的互斥量

1.1.3互斥量加锁

1.1.4互斥量解锁

1.2利用RAII思想封装一个管理互斥量的对象  

1.3如何保证申请锁的过程是原子的?

2.Linux线程同步

2.1条件变量

2.1.1初始化条件变量

2.1.2销毁动态分配的条件变量

2.1.3等待条件变量满足 

2.1.4唤醒等待的线程

2.2条件变量函数使用规范

3.可重入VS线程安全

3.1概念

3.2常见的线程不安全的情况

3.3常见的线程安全的情况

3.4常见的不可重入的情况

3.5常见的可重入的情况

3.6可重入与线程安全联系

3.7可重入与线程安全区别

4.常见锁概念

4.1死锁

4.2死锁的四个必要条件


前言

本篇文章内容:线程互斥、互斥量的使用、线程同步、条件变量的使用、可重入函数与线程安全相关内容。

欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.Linux线程互斥

首先我们先来学习一组概念:

  • 临界资源: 多线程执行流共享的资源叫做临界资源(全局变量)。
  • 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区(访问或修改临界资源的代码)。
  • 互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

原子性:如果操作只有一条汇编指令,那么该操作就是原子的。

为了更好的理解,这里模拟实现一个抢票系统,我们将记录票的剩余张数的变量定义为全局变量,主线程创建四个新线程,让这四个新线程进行抢票,当票被抢完后这四个线程自动退出。

#include <iostream>
#include <unistd.h>
#include <pthread.h>

int tickets = 1000;
void *route(void *arg)
{
  const char *name = (char *)arg;
  while (1)
  {
    if (tickets > 0)
    {
      usleep(10000);
      std::cout << name << " get a ticket, remain: " << --tickets << std::endl;
    }
    else
    {
      break;
    }
  }
  std::cout << name << "quit!" << std::endl;
  pthread_exit((void *)0);
}
int main()
{
  pthread_t t1, t2, t3, t4;
  pthread_create(&t1, NULL, route, (void *)"thread 1");
  pthread_create(&t2, NULL, route, (void *)"thread 2");
  pthread_create(&t3, NULL, route, (void *)"thread 3");
  pthread_create(&t4, NULL, route, (void *)"thread 4");

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

 奇怪的是,最后剩余的票数为负值。

我们明明判断了当tickets>0时,才会对总票数--,可为什么这里是负值呢?

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

--tickets的操作并不是原子操作,因为它对应着三条汇编指令:

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

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

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

要做到以上三点,本质上就是需要一把锁,进入临界区之前加锁,离开临界区后解锁,Linux上提供的这把锁叫做互斥量mutex


1.1互斥量的接口

有关于互斥量的操作非常简单,初始化互斥量,销毁互斥量,上锁,解锁等,并且我们还可以利用RAII的思想来管理动态分配的互斥量,具体如何使用都可以。


1.1.1初始化互斥量

对互斥量的初始化,我们有两种方式,一种是静态分配,另一种是动态分配。

静态分配就是当互斥量是全局或者static时使用:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//PTHREAD_MUTEX_INITIALIZER是一个宏

对于这种全局或static互斥量,不需要销毁。


动态分配就是当互斥量是局部时,利用pthread_mutex_init函数初始化,然后使用(动态分配的互斥量需要销毁): 

int pthread_mutex_init(pthread_mutex_t *restrict mutex
                , const pthread_mutexattr_t *restrict attr);

参数说明:

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

返回值说明:

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

 1.1.2销毁动态分配的互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

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

返回值说明:

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

销毁互斥量需要注意:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。

1.1.3互斥量加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数说明:

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

返回值说明:

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

调用pthread_mutex_lock时,可能会遇到以下情况:

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

1.1.4互斥量解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数说明:

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

返回值说明:

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

1.2利用RAII思想封装一个管理互斥量的对象  

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__

#include <iostream>
#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

#endif

 有了这个类,你就可以将互斥量交给该类管理,当该类构造时,进行加锁,当该类析构时解锁。


1.3如何保证申请锁的过程是原子的?

上面大家已经意识到了--和++操作不是原子操作,可能会导致数据不一致问题。

其实为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用就是把寄存器和内存单元的数据相交换。

由于只有一条汇编指令,保证了原子性。

加锁和解锁的过程我们可以通过下面的伪代码来理解:

下面我们再以图示来理解:

 图示中我们在内存中定义了一个mutex互斥量,当我们进行加锁时,cpu会原子地将两个数据进行交换, 交换后,内存中变为0,寄存器%al中变为1,并且这个1的值是唯一的,因为数据在内存中时,所有线程都能访问,属于共享的,但是如果转移到CPU内部寄存器中,就属于一个线程私有的了,因为CPU寄存器内部的数据是线程的硬件上下文。

所以这个1的值就是唯一的,当任何一个线程将这个1交换走后(交换是原子的),哪怕此时线程的时间片到了被切换走,这个1也被线程作为硬件上下文带走了,别的线程也拿不到,判断时也会认为该锁被占用了。


2.Linux线程同步

同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步。

那么什么情况下需要同步呢?

如果个别的线程竞争能力非常强, 每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题。

那如何解决呢?

我们可以让线程每次释放锁后,不能立即申请锁,而是去排队,有顺序的申请锁。


2.1条件变量

条件变量就是实现线程同步的解决方案,是用来描述某种资源是否就绪的一种数据化描述。

换句话说,我们可以利用条件变量实现对其他线程的控制。

比如:

  • 控制某个线程在某一条件变量下等待;
  • 条件满足后,对该线程进行唤醒。

2.1.1初始化条件变量

对条件变量的初始化,我们有两种方式,一种是静态分配,另一种是动态分配。

静态分配就是当条件变量是全局或者static时使用:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//PTHREAD_COND_INITIALIZER是一个宏

对于这种全局或static互斥量,不需要销毁。


动态分配就是当条件变量是局部时,利用pthread_cond_init函数初始化,然后使用(动态分配的条件变量需要销毁): 

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

参数说明:

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

返回值说明:

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

2.1.2销毁动态分配的条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

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

返回值说明:

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

销毁互斥量需要注意:

  • 使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁。

2.1.3等待条件变量满足 

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

参数说明:

  • cond:让线程在该条件变量下等待。
  • mutex:当前线程所处临界区对应的互斥锁(为什么要传互斥锁???)。

返回值说明:

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

2.1.4唤醒等待的线程

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,失败返回错误码。

等待条件变量满足的函数参数为什么要传互斥锁?

  • 首先你需要明确的是,使用条件变量一定会搭配使用互斥锁,因为线程同步的场景本身就是在互斥的前提下,即两个线程访问同一资源(临界资源),而现在需要保证的是资源使用的顺序性,所以才引入了条件变量。
  • 而如果此时某个线程由于条件并不满足(这个条件不满足一定是临界资源不满足该线程运行的条件),被设置了等待条件变量满足,从而进入了阻塞状态,能使条件满足一定是该临界资源被修改了,从而满足了线程的运行需要,所以此时你就可以唤醒等待的线程。
  • 也就是说我们想要让条件得到满足,就一定会修改临界资源,而如果你在等待条件变量满足的时候,仍然持有着该临界资源的锁,那么就会导致其他能使该临界资源满足线程运行需要的其他线程访问不了该临界资源,所以给等待条件变量满足的函数传入互斥锁的目的就是让这个锁临时被释放,让其他线程可以访问该临界资源

总结:

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

2.2条件变量函数使用规范

等待条件变量的代码

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);

3.可重入VS线程安全

3.1概念

  • 线程安全: 多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全问题。
  • 重入: 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

注意: 线程安全讨论的是线程执行代码时是否安全,重入讨论的是函数被重入进入。

3.2常见的线程不安全的情况

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

3.3常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

3.4常见的不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

3.5常见的可重入的情况

  • 不使用全局变量或静态变量。
  • 不使用malloc或者new开辟出的空间。
  • 不调用不可重入函数。
  • 不返回静态或全局数据,所有数据都由函数的调用者提供。
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

3.6可重入与线程安全联系

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

3.7可重入与线程安全区别

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

可重入函数一定是线程安全的,函数不可重入是引发线程安全问题的一种常见情况。 


4.常见锁概念

4.1死锁

死锁(Deadlock)是数据库系统、操作系统或并发编程中常见的一种现象,它指的是两个或两个以上的进程(或线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法向前推进。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

举例:假设有两个进程P1和P2,它们都需要两个资源R1和R2。如果P1获得了R1并请求R2,同时P2获得了R2并请求R1,那么这两个进程都会阻塞等待对方释放资源,从而导致死锁。

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

单执行流也有可能产生死锁,如果某一执行流连续申请了两次锁,那么此时该执行流就会被挂起。

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

4.2死锁的四个必要条件

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

注意: 这是死锁的四个必要条件,也就是说只有同时满足了这四个条件才可能产生死锁。

避免死锁

  • 破坏死锁的四个必要条件。
  • 按顺序申请资源。
  • 避免锁未释放的场景。
  • 资源一次性分配。

除此之外,还有一些避免死锁的算法,比如死锁检测算法和银行家算法。


 =========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

明星中药企业系列洞察(八)解题!仲景宛西制药如何奋力打造百亿级大健康产业?

近日&#xff0c;仲景宛西制药携六味地黄丸、逍遥丸等经典产品亮相第 88 届全国药品交易会。从最初的百泉药交会、樟树药交会&#xff0c;到郑州全国药品交易会&#xff0c;再到今年&#xff08;2024 年&#xff09;上海药交会&#xff0c;仲景宛西制药在品牌塑造&#xff0c;产…

Nature:使用语义熵检测大语言模型中的幻觉

使用语义熵检测大语言模型中的幻觉 Detecting hallucinations in large language models using semantic entropy 论文阅读摘要研究目标论文图表概述总结关键解决方案语义熵计算:虚构内容检测: 双向蕴涵在大语言模型中的应用上下文的重要性蕴涵估计器 实验设计语义熵计算步骤结…

【CT】LeetCode手撕—1143. 最长公共子序列

目录 题目1- 思路2- 实现⭐1143. 最长公共子序列——题解思路 3- ACM 实现 题目 原题连接&#xff1a;1143. 最长公共子序列 1- 思路 模式识别&#xff1a;最长公共子序列——> 动规五部曲 2- 实现 ⭐1143. 最长公共子序列——题解思路 class Solution {public int longe…

Android14之RRO资源文件替换策略(二百二十一)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

Python 获取字典中的值(八种方法)

Python 字典(dictionary)是一种可变容器模型&#xff0c;可以存储任意数量的任意类型的数据。字典通常用于存储键值对&#xff0c;每个元素由一个键&#xff08;key&#xff09;和一个值(value&#xff09;组成&#xff0c;键和值之间用冒号分隔。 以下是 Python 字典取值的几…

vite项目如何在本地启动https协议

vite项目如何在本地启动https协议 本地启动正常配置在vite.config.js文件中默认启动http协议的请求&#xff0c;如何改成https呢&#xff1f;今天的开发中遇到了这个问题项目需求&#xff1a; 本地启动https协议的前端页面并且正常访问后台https协议的接口 解决方法&#xff1a…

微尺度气象数值模拟—WRF-LES大涡模拟;NDOWN工具使用;PALM编译、运行;PALM静态数据预备

针对微尺度气象的复杂性&#xff0c;大涡模拟&#xff08;LES&#xff09;提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟&#xff0c;这些过程往往与天气模式、地形影响和人为因素如城市布局紧密相关。在这种规模上&#xff0c;传统的气象模…

名企面试必问30题(十二)——简单介绍一下你的家庭情况

1.思路 对于面试官来说&#xff0c;他提出这个问题&#xff0c;只是为了深挖您的性格、稳定性、行事风格&#xff0c;包括未来定居规划、生育规划等基础信息&#xff0c;这是正常情况。您不要过多围绕其他家庭成员来讲&#xff0c;否则面试官无法获取他想要的&#xff0c;您也难…

新手第一个漏洞复现:MS17-010(永恒之蓝)

文章目录 漏洞原理漏洞影响范围复现环境复现步骤 漏洞原理 漏洞出现在Windows SMB v1中的内核态函数srv!SrvOs2FeaListToNt在处理FEA&#xff08;File Extended Attributes&#xff09;转换时。该函数在将FEA list转换成NTFEA&#xff08;Windows NT FEA&#xff09;list前&am…

2024新版大屏互动微信上墙源码大屏互动神器年会婚庆微现场3D签到

2024年大屏幕互动源码动态背景图和配乐素材搭建教程 php宝塔搭建部署活动现场大屏幕互动系统php源码&#xff0c;可以用来构建具有互动功能的大屏幕系统&#xff0c;为活动现场提供各种互动体验。 修复版的系统源码在原有功能的基础上&#xff0c;增加了更加完善的用户体验和稳…

【SGX系列教程】(二)第一个 SGX 程序: HelloWorld,linux下运行

文章目录 0. SGX基础原理分析一.准备工作1.1 前提条件1.2 SGX IDE1.3 基本原理 二.程序设计2.1 目录结构2.2 源码设计2.2.1 Encalve/Enclave.edl:Enclave Description Language2.2.2 Enclave/Enclave.lds: Enclave linker script2.2.3 Enclave/Enclave.config.xml: Enclave 配置…

深入理解Java核心技术模块化局部变量类型推断

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

Python计算n的阶乘的多种方法

1 问题 在课上&#xff0c;我们学习了用递归函数去计算一个自然数n的阶乘。但计算一个自然数n的阶乘是否就这一种方法呢&#xff1f; 2 方法 关于计算一个自然数n的阶乘&#xff0c;通过搜索&#xff0c;我们寻找到了除运用递归函数外的其他三种方法&#xff1a; 方法一 通过导…

【PyQt5】一文向您详细介绍 QLineEdit() 的作用

【PyQt5】一文向您详细介绍 QLineEdit() 的作用 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本硕&…

树洞陪聊陪玩交友程序系统源码,解锁交友新体验

在繁忙的都市生活中&#xff0c;你是否渴望找到一片属于自己的秘密花园&#xff0c;倾诉心声、分享快乐&#xff1f;今天&#xff0c;就让我带你走进这片名为“树洞”的神秘之地&#xff0c;感受陪聊陪玩交友的全新魅力&#xff01; &#x1f333;树洞陪聊陪玩交友程序系统 你…

基于CST2024 Python内部环境的双锥天线自动3D建模和仿真

CST Studio Suite 2024版里面的Python相较于之前有了大的变化。 第一&#xff0c; 增加了cst.asymptotic &#xff0c;cst.radar &#xff0c;cst.units 三个包。 第二&#xff0c;之前CST python只能通过外部环境去操作&#xff0c;现在增加了内部环境控制&#xff0c;可以内…

修改element-ui日期下拉框datetimePicker的背景色样式

如图&#xff1a; 1、修改背景色 .el-date-picker.has-sidebar.has-time { background: #04308D; color: #fff; border: 1px solid #326AFF } .el-date-picker__header-label { color: #ffffff; } .el-date-table th { color: #fff; } .el-icon-d-arrow-left:before { color: …

MyBatis操作数据库(入门)

本节目标 使用MyBatis完成简单的增删改查操作&#xff0c;参数传递掌握MyBatis的两种写法&#xff1a;注解和XML方式掌握MyBatis相关的日志配置 前言 在应用分层学习中&#xff0c;我们了解web应用程序一般分为三层&#xff0c;即Controller、Service、Dao。在之前的案例中&a…

使用Nginx反向代理KKFileView遇到问题

使用KKFileView 4.0 以上版本 在KKFileView官网上&#xff0c;关于使用Nginx代理&#xff0c;建议配置如下 一、修改Nacos 在Nginx的conf文件夹中修改 nginx.conf ,新加 红框内的IP地址为代理服务器地址&#xff08;即安装KKFileView的服务器地址&#xff09; 二、修改KKFil…

计算机组成原理——寄存器

文章目录 1. 寄存器 2. 带寄存器的加法器 3. 时钟信号与计算速度 1. 寄存器 上一篇D触发器可以在时钟上沿存储1位数据。如果想存储多个位&#xff08;bit&#xff09;的数据&#xff0c;就需要用多个D触发器并联实现&#xff0c;这种电路称之为寄存器。 寄存器是计算机中央…