[Linux]:线程(二)

news2024/11/18 19:47:24

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

Windows环境不同,我们在linux环境下需要通过指令进行各操作,以下是常见操作的指令:

1. 线程互斥

1.1 基本概念

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

进程之间进行通信需要先创建第三方资源,使得不同的进程能够看到同一份资源。由于这份第三方资源可以由操作系统中的不同模块提供,所以进程间通信的方式有很多种。在进程间通信中,这个第三方资源被称为临界资源,而访问第三方资源的代码则被称为临界区。

与之不同的是,多线程的大部分资源都是共享的。因此,线程之间进行通信并不需要像进程那样费力地去创建第三方资源。

例如,我们在代码中只需要在全局区定义一个count变量,新线程可以每隔一秒对该变量进行加一操作,主线程也可以每隔一秒获取count变量的值并进行打印。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
using namespace std;
int count = 0;
void *Routine(void *args)
{
    while (true)
    {
        count++;
        sleep(1);
    }
    pthread_exit((void *)0);
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, Routine, nullptr);
    while (true)
    {
        cout << "The value of count is " << count << endl;
        sleep(1);
    }
    pthread_join(tid, nullptr);
    return 0;
}

在当前情境下,我们相当于实现了主线程和新线程之间的通信。其中,全局变量count起着关键作用,它被称为为临界资源,原因在于它被多个执行流所共享。而主线程中的 cout 操作以及新线程中的 count++ 操作,被称作临界区。这是因为这些代码片段对临界资源进行了访问。

但是我们同样观察到打印数据并没有 1,这就是多执行流对临界资源操作常引发的数据不一致问题。

同样我们也可以下面抢票程序的实现,具体演示如果不对临界资源进行限制,可能会出现的危害。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
void *getTickets(void *args)
{
    string name = "thread ";
    name += to_string((uint64_t)args);
    while (true)
    {
        if (tickets > 0)
        {
            usleep(1000);
            cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;
        }
        else
        {
            break;
        }
    }
    cout << name << " is quit!" << endl;
    pthread_exit((void *)0);
}
int main()
{
    pthread_t tids[5];
    for (uint64_t i = 0; i < 5; i++)
    {
        pthread_create(tids + i, nullptr, getTickets, (void *)i);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(tids[i], nullptr);
    }
    return 0;
}

剩余票数出现负数,这明显不符合我们的常识与预期,之所以出现这种情况,本质就是 tickets就是我们的临界资源,--tickets也 并不是原子的,在多执行流同时执行时就可能会发生这种问题。

为什么 --tickets并不是原子的呢?

因为从汇编角度看,我们的 --操作其实是不安全的,他们转成汇编,一般会对应三条汇编指令:从内存中读取数据到 CPU 中;CPU 内进行操作;CPU 将结果写回内存。进程在运行的时候,随时可能被切换。

1.2 互斥量

为了解决这个问题我们就引入了互斥,保证一次只有一个执行流访问临界资源,而为了实现互斥,我们就需要保证临界区的原子性,即临界区的资源要么被执行完成,要么不执行,只存在这两态。

要做到这些,本质就是需要一把锁,所以 Linux就引入一个锁,并将其称为互斥量。

画板

1.3 互斥量的接口

1.3.1 初始化互斥量

我们可以使用pthread_mutex_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
  2. 参数:
  • mutex:需要初始化的互斥量。
  • attr:初始化互斥量的属性,一般设置为 nullptr 即可。
  1. 返回值:互斥量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化互斥量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

1.3.2 销毁互斥量

我们可以使用pthread_mutex_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
  2. 参数:mutex:需要销毁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

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

  • 使用PTHREAD_MUTEX_INITIALIZER静态初始化的互斥量不需要销毁。
  • 不能销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。
1.3.3 加锁互斥量

加锁本质就是让被加锁区域的代码具有原子性,只能同时被一个线程访问。我们可以使用pthread_mutex_lock对互斥量进行加锁,使用方法如下:

  1. 函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要加锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

如果一个线程在执行过程中,遇见该接口,并且该锁已被其他线程申请,那么该线程此时就会陷入阻塞状态,等待其解锁。

1.3.4 解锁互斥量

在加完锁之后,我们不可能让所有代码只被一个执行流访问,所以我们需要合适的地方解锁,我们可以使用pthread_mutex_unlock对互斥量进行解锁,使用方法如下:

  1. 函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
  2. 参数:mutex:需要解锁的互斥量。
  3. 返回值:成功返回 0,失败返回错误码。

知道了这些概念之后我们就可以对前面的抢票逻辑进行修改:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
int tickets = 1000;
pthread_mutex_t mutex;
void *getTickets(void *args)
{
    string name = "thread ";
    name += to_string((uint64_t)args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        if (tickets > 0)
        {
            usleep(1000);
            cout << "[" << name << "]" << "get a ticket,left: " << --tickets << endl;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
    cout << name << " is quit!" << endl;
    pthread_exit((void *)0);
}
int main()
{
    pthread_mutex_init(&mutex, nullptr);
    pthread_t tids[5];
    for (uint64_t i = 0; i < 5; i++)
    {
        pthread_create(tids + i, nullptr, getTickets, (void *)i);
    }
    for (int i = 0; i < 5; i++)
    {
        pthread_join(tids[i], nullptr);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

其实在大部分情况下,加锁本身都是有损于性能的事,因为它使多执行流由并行执行变为了串行执行,这几乎是不可避免的。所以我们需要在合适的位置加锁与解锁,尽可能减少锁引入锁带来的性能开销成本。

1.4 互斥量的原理

当我们使用互斥量之后,临界区的代码对于其他线程来说,只有两种状态:加锁与解锁,这就保证了临界区的原子性。而我们要知道锁本身就是能被所有执行流访问的资源,所以锁本身也是一种临界资源,当然也需要保证其原子性,所以锁本身实现就是原子的。

为了实现互斥锁操作,大多数体系结构都提供了 swapexchange 指令,该指令的作用就是把寄存器和内存单元的数据相交换,以下就是实现加锁 lock与解锁 unlock的伪代码:

我们首先可以认为 mutex 的初始值为1,al 是计算机中的一个寄存器,当线程申请锁时,需要执行以下步骤:

  1. 先将 al 寄存器中的值清0。该动作可以被多个线程同时执行,因为每个线程都有自己的一组寄存器(上下文信息),执行该动作本质上是将自己的 al 寄存器清0。
  2. 然后交换 al 寄存器和 mutex 中的值。xchgb 是体系结构提供的交换指令,该指令可以完成寄存器和内存单元之间数据的交换。

画板

  1. 最后判断 al 寄存器中的值是否大于0。若大于0则申请锁成功,此时就可以进入临界区访问对应的临界资源;否则申请锁失败需要被挂起等待,直到锁被释放后再次竞争申请锁。

我们需要注意的是CPU内的寄存器不是被所有的线程共享的,每个线程都有独自的一组寄存器,所以改变当前线程 al寄存器的值并不会影响其他线程的 al寄存器, 当然内存中的数据因为属于同一个进程,所以各个线程是共享的。

画板

而当线程释放锁时,需要执行以下步骤:

  1. 将内存中的 mutex 置回1,使得下一个申请锁的线程在执行交换指令后能够得到1。
  2. 唤醒等待 mutex 的线程,让它们继续竞争申请锁。

在线程释放锁的过程中,并没有将当前线程的 al 寄存器中的值清0,这不会造成任何影响,因为每次线程在申请锁时都会先将自己 al 寄存器中的值清0,再执行交换指令。

所以我们申请锁的本质就是执行 xchgb这一条汇编指令,因为只有一条,所以只有已执行与未执行两种状态,具有原子性。

2. 线程安全

线程安全是指在多线程环境下,多个线程并发执行同一段代码时,不会出现不可预期的错误结果或数据不一致的情况。

常见线程不安全的情况有:

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

而线程安全的情况有:

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

而可重入函数与线程安全的联系有:

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

而可重入函数与线程安全的区别有:

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

3. 死锁

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

比如说如果某一执行流连续申请了两次锁,就会陷入死锁状态。具体情况如下:当该执行流第一次申请锁时,通常会申请成功。然而,第二次申请锁时,由于此锁已经被该执行流自身持有,再次申请会失败,进而导致该执行流被挂起。而此时,这个锁在其自己手上,可它又处于被挂起的状态,根本没有机会去释放锁。这样一来,该执行流将永远无法被唤醒,从而处于死锁状态。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void*Routine(void*args)
{
    pthread_mutex_lock(&mutex);
    pthread_mutex_lock(&mutex);
    return nullptr;
}
int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,Routine,nullptr);
    pthread_join(tid,nullptr);
    return 0;
}

其中形成死锁的必要条件有以下四个:

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

而为了避免死锁我们一般也可以从这几个角度思考:

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

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

4. 线程同步

4.1 饥饿问题

线程饥饿指的是某些线程由于各种原因,一直无法获得足够的 CPU 时间来执行任务,从而处于长期等待或执行时间极少的状态。 产生线程饥饿的原因主要有以下几种:

  1. 高优先级线程抢占:如果系统中有高优先级的线程持续占用 CPU 资源,那么低优先级的线程就可能长时间得不到执行机会,从而导致饥饿。例如,在实时系统中,高优先级的实时任务可能会一直抢占低优先级的普通任务。
  2. 线程调度不公平:如果线程调度算法不合理或者存在缺陷,可能导致某些线程被不公平地对待,长期无法获得执行机会。比如某些调度算法可能偏向于某些特定类型的线程或者特定状态的线程。
  3. 资源竞争:当多个线程竞争有限的资源时,一些线程可能因为一直无法获得所需资源而被阻塞,从而无法执行。例如,多个线程竞争一个互斥锁,而某些线程总是在竞争中失败,就可能陷入饥饿状态。

线程饥饿会导致系统性能下降,部分任务无法及时完成,甚至可能使整个系统陷入停滞或出现不可预测的行为。为了解决线程饥饿问题,我们可以让线程与线程之间形成同步关系。

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

4.2 条件变量

条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述。其一般包含两个步骤:

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

4.3 条件变量的接口

4.3.1 初始化条件变量

我们可以使用pthread_cond_init初始化互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  2. 参数:
  • cond:需要初始化的条件变量。
  • attr:初始化条件变量的属性,一般设置为 nullptr 即可。
  1. 返回值:条件变量初始化成功返回0,失败返回错误码。

这种调用函数接口初始化条件变量的方式我们称为动态分配,除此之外,我们也能使用如下的方式进行初始化,我们将其称为静态分配。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

4.3.2 销毁条件变量

我们可以使用pthread_cond_destory销毁互斥量,使用方法如下:

  1. 函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
  2. 参数:mutex:需要销毁的条件变量。
  3. 返回值:成功返回 0,失败返回错误码。
  • 使用 PTHREAD_COND_INITIALIZER 静态初始化的条件变量不需要销毁。
4.3.3 等待条件变量

当某个线程满足某个条件时,我们就可以将其至于条件变量下等待。

  1. 函数原型:int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  2. 参数:
  • cond:需要等待的条件变量。
  • mutex:当前线程所处临界区对应的互斥锁。
  1. 返回值:成功返回 0,失败返回错误码。
4.3.4 唤醒等待

在满足某个条件之后,我们就可以使用以下两种即可,将等待队列中的线程唤醒。

  1. 函数原型:
  • int pthread_cond_broadcast(pthread_cond_t *cond);
  • int pthread_cond_signal(pthread_cond_t *cond);
  1. 参数:cond:需要唤醒的条件变量。
  2. 返回值:成功返回 0,失败返回错误码。

其中 pthread_cond_signal()函数用于唤醒等待队列中的第一个线程。pthread_cond_broadcast()函数用于唤醒等待队列中的全部线程。

比如我们下面创建五个线程,然后将其放入等待队列,最后由主线程进行唤醒。

#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#include <string>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *Routine(void *args)
{
    pthread_detach(pthread_self());
    string name = "thread " + to_string((uint64_t)args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        cout << name << " running..." << endl;
        pthread_mutex_unlock(&mutex);
    }
}
int main()
{
    pthread_t tids[5];
    pthread_mutex_init(&mutex, nullptr);
    pthread_cond_init(&cond, nullptr);
    for (uint64_t i = 0; i < 5; i++)
    {
        pthread_create(tids + i, nullptr, Routine, (void *)i);
    }
    while (true)
    {
        sleep(1);
        pthread_cond_signal(&cond);
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

在调用<font style="color:rgb(28, 31, 35);">pthread_cond_wait</font>函数时需要传入对应的互斥锁,原因如下:

当线程由于某些条件不满足而需要在特定条件变量下进行等待时,必须释放该互斥锁。这是因为如果不释放互斥锁,其他线程将无法获取该锁以进入临界区修改共享资源,从而无法改变条件使等待线程被唤醒。

当该线程被唤醒后,会接着执行临界区内的代码,这就要求该线程必须立即获得对应的互斥锁。这样设计确保了线程在被唤醒后能够安全地访问临界区,避免了多个线程同时进入临界区而导致的数据不一致和资源竞争问题。

4.4 条件变量使用规范

使用条件变量我们一般遵守以下规范,如果是等待条件变量,函数应该放在互斥量加锁与解锁之间,因为判断条件也是一种临界资源。

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.coloradmin.cn/o/2178820.html

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

相关文章

LQR算法核心思想

本章以倒立摆为解决目的 什么是线性二次型控制器&#xff08;LQR&#xff09; 开环系统 即状态变量的倒数 系统的状态空间矩阵A * 系统状态变量x A状态矩阵&#xff1a;描述系统本身物理特性的一个矩阵&#xff0c;它是由系统本身的机械结构、物理结构决定的&#xff0c;无法…

基于单片机8路数字电压表电压采集系统

**单片机设计介绍&#xff0c;基于单片机8路数字电压表电压采集系统 文章目录 前言概要功能设计设计思路 软件设计效果图 程序设计程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技…

【C#】BotSharp:开源机器学习平台

随着人工智能&#xff08;AI&#xff09;和自然语言处理&#xff08;NLP&#xff09;技术的迅速发展&#xff0c;聊天机器人已经成为现代应用和服务的重要组成部分。无论是智能客服、虚拟助手&#xff0c;还是业务自动化和数据分析&#xff0c;智能对话系统正在各个领域发挥重要…

【2024.9.29练习】R 格式

题目描述 题目分析 带小数点的高精度乘法。小数点在计算时忽略&#xff0c;只需在最终打印字符串的时候在合适位置四舍五入即可。对于&#xff0c;可理解为对d乘2总共n次。因此使用“单精度高精度”类型的算法足矣。 我的代码 一开始代码有错误&#xff0c;我只想到了对小数点…

GAMES101(作业8)

作业8 题目&#xff1a; 模拟绳子动画&#xff0c;包括基于物理的&#xff0c;和非物理的&#xff0c;应该修改的函数是:rope.cpp 中的void Rope::simulateEuler(... Rope::rope(...)&#xff0c;&#xff0c;void Rope::simulateVerlet(...) 代码框架&#xff1a; main:负…

9.26-9.29学习

一.项目结构的建立 5个微服务模块 新建好各个模块后&#xff0c;在项目pom下引入各模块。各pom文件指定springboot版本2.1.8.RELEASE .gitignore #表示任意路径下的xx文件 **/mvnw **/mvnw.cmd**/.mvn **/target/.idea**/.gitignore 二.数据库初始化 一个微服务模块对应一个数…

微信小程序 蓝牙通讯

客户的需求如下&#xff1a;通过微信小程序控制蓝牙ble设备(电子面膜)&#xff0c;通过不同指令控制面膜的亮度和时间。 01.首先看下客户的ble设备服务文档&#xff1a;(本部分需要有点蓝牙基础,在调试过程中可以用安卓软件nRF Connect软件来执行测试命令) 0xFFF1灯控命令 命…

PCL 法线空间采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 法线计算 2.1.2 基于法线进行采样 2.1.3 可视化原始点云和采样后的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实…

英伟达的AI一键生成数字人物理运动动画框架:统一控制模式,提升交互性和沉浸感

在虚拟现实(VR)、增强现实(AR)和3D内容创作领域,创建具有真实感和动态性的虚拟角色一直是技术上的挑战。最近,英伟达推出了一种新的框架,通过将物理驱动的角色控制视为运动修复问题,实现了跨场景的虚拟角色控制。这一创新方法不仅支持多种控制模式,还能够生成连贯且自…

container_of 函数的分析

这个函数的目的是&#xff0c; 通过结构体里面的内容 找到 大结构体的 基地址。 函数的原型是&#xff1a;  &#xff30;&#xff34;&#xff32;是指针 &#xff54;&#xff59;&#xff50;&#xff45; &#xff0c; &#xff4d;&#xff45;&#xff4d;&#xff…

PCL 快速均匀下采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 快速均匀下采样 2.1.2 可视化原始点云和下采样后的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#…

恋爱辅助应用小程序app开发之广告策略

恋爱话术小程序带流量主广告开启&#xff0c;是一个有效的盈利模式&#xff0c;可以增加小程序的收入来源。以下是对此的详细分析 一、流量主广告的定义与优势 流量主广告是指在小程序中嵌入广告位&#xff0c;通过展示广告内容来获取广告主的付费。对于恋爱话术小程序而言&am…

图解C#高级教程(一):委托

什么是委托 可以认为委托是持有一个或多个方法的对象。但它与对象不同&#xff0c;因为委托可以被执行。当执行委托时&#xff0c;委托会执行它所“持有”的方法。先看一个完整的使用示例。 // See https://aka.ms/new-console-template for more informationdelegate void M…

无人机避障—— 激光雷达定高北醒TF03-UART(二)

无人机避障过程&#xff0c;光靠大疆飞控内部的气压计不准&#xff0c;很容易在高度较低的时候受到地面植被等障碍物影响&#xff0c;使得掉高严重&#xff0c;因此采用激光雷达定高模块进行定高。 硬件&#xff1a; 北醒TF03-UART、Xavier-NX 软件代码&#xff1a; 北醒官…

关于没有启用root问题,分区表挂载错误,导致系统无法启动

方法一、root没有登陆过&#xff0c;改root密码 1、为啥这样设置&#xff0c;root 2、密码破解也无效 2.1、开机启动&#xff0c;按 e 进入启动文件界面 2.2、把ro修改为rw&#xff0c;注意r和o之间包了个反斜杠 2.3、ctrl x退出当前模式 2.4、rw initsysroot/bin/sh 2.5、c…

HarmonyOs 查看官方文档使用弹窗

1. 学会查看官方文档 HarmonyOS跟上网上的视频学习一段时间后&#xff0c;基本也就入门了&#xff0c;但是有一些操作网上没有找到合适教学的视频&#xff0c;这时&#xff0c;大家就需要养成参考官方文档的习惯了&#xff0c;因为官方的开发文档是我们学习深度任何一门语言或…

http请求过程 part-2

http请求过程 http应用层 实体 实体分为实体首部和实体主体&#xff0c;实体首部是用来描述主体的 实体部分是可选的&#xff0c;它被用来运送请求或者响应的数据 传输层-TCP HTTP连接是建立在TCP连接的基础上 以流形式通过一条已经打开的TCP连接&#xff0c;按顺序进行…

Django Web开发接口定义

Django Web 介绍 Django Web是一个Pyhton高级 Web 框架,实际上 Django 也可以做到前后端分离,即主要作为后端框架使用,不用模板渲染也是可行的。 Django Web 应用的运行流程,如下图所示: 此外,Django Web 在开发环境可以通过自带的服务器进行本地调试。但是该服务器不适…

LeetCode从入门到超凡(五)深入浅出---位运算

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的LeetCode学习总结文档&#xff1b;本文主要讲解 位运算算法。&#x1f495;&#x1f495;&#x1f60a; 一、 位运算简介 1.什么是位…

【腾讯元宝-免费论文精读】

【腾讯元宝-免费论文精读】 1. 腾讯混元大模型2. 论文精读过程3. 总结&#xff1a; 1. 腾讯混元大模型 由腾讯研发的大语言模型&#xff0c;具备强大的中文创作能力&#xff0c; 复杂语境下的逻辑推理能力&#xff0c;以及可靠的任务执行能力 腾讯元宝&#xff1a;轻松工作&am…