【Linux】线程互斥与同步

🔥 个人主页:大耳朵土土垚
🔥 所属专栏:Linux系统编程

这里将会不定期更新有关Linux的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

文章目录

  • 1. 进程线程间的互斥相关背景概念
  • 2. 互斥量mutex
  • 3. 互斥量mutex接口
    • 3.1 初始化互斥量
    • 3.2 销毁互斥量
    • 3.3 互斥量加锁和解锁
    • 3.4 互斥量的封装
  • 4. 线程同步
    • 同步概念与竞态条件
    • 条件变量
  • 5. 条件变量函数
    • 5.1 初始化
    • 5.2 销毁
    • 5.3 等待条件满足
    • 5.4 唤醒等待
    • 5.5 条件变量封装
    • 5.6 条件变量使用规范
  • 6. 生产者消费者模型
    • 基于BlockingQueue的生产者消费者模型
  • 7. 结语

1. 进程线程间的互斥相关背景概念

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

2. 互斥量mutex

  有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。然而多个线程并发的操作共享变量,会带来⼀些问题。

共享变量被保护起来后就可以称之为临界资源

例如如下代码:

// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
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()
{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);
}

会出现以下可能的结果:

thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2

  为什么会出现票数为负数的情况?

  • 这是因为if 语句判断条件为真以后,代码可以并发的切换到其他线程,其他线程可能将最后一张票抢完后又切换回来,此时剩余票数为0,但是当前线程已经在if判断语句内了,还会进行票数-1,此时剩余票数就会减到负数;
  • 此外usleep 这个模拟漫长业务的过程,在这个业务过程中,可能有很多个线程会进⼊该代码段…
  • 综上所述,最根本在于--ticket 操作本⾝就不是⼀个原子操作

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

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

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

如下图所示:
在这里插入图片描述

3. 互斥量mutex接口

3.1 初始化互斥量

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

  • 方法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

3.2 销毁互斥量

销毁互斥量需要注意:

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

3.3 互斥量加锁和解锁

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( void )
{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);
}

通过对公共资源的加锁保护我们就可以保证访问资源的原子性,这样就不会出现之前的错误了。

3.4 互斥量的封装

  通过上述互斥量mutex接口的学习,我们就可以基于上述函数对互斥量进行封装:

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>namespace MutexModule
{class Mutex{public:// 删除不要的拷⻉和赋值Mutex(const Mutex &) = delete;const Mutex &operator=(const Mutex &) = delete;Mutex(){// 初始化锁int n = pthread_mutex_init(&_mutex, nullptr);if (n != 0)std::perror("初始化失败...");}void Lock(){int n = pthread_mutex_lock(&_mutex);if (n != 0)std::perror("Lock失败...");}void Unlock(){int n = pthread_mutex_unlock(&_mutex);if (n != 0)std::perror("Unlock失败...");}pthread_mutex_t *MutexPtr() // 获取原始指针{return &_mutex;}~Mutex(){int n = pthread_mutex_destroy(&_mutex);if (n != 0)std::perror("_mutex销毁失败...");}private:pthread_mutex_t _mutex;};// 采⽤RAII⻛格,进⾏锁管理class LockGuard{public:LockGuard(Mutex &mutex) : _mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}private:Mutex &_mutex;};
}

封装好互斥量后我们再采用RAII智能指针的风格,对锁进行管理。

  • 抢票的代码就可以更新成为:
// 抢票的代码就可以更新成为
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "Lock.hpp"
using namespace MutexModule;
int ticket = 1000;Mutex mutex;
void *route(void *arg)
{char *id = (char *)arg;while (1){LockGuard lockguard(mutex); // 使⽤RAII⻛格的锁if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}
int main(void)
{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);
}

4. 线程同步

同步概念与竞态条件

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

条件变量

  • 当⼀个线程互斥地访问某个变量时,发现在其它线程改变状态之前,它什么也做不了。例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列中。这种情况就需要用到条件变量。

多个线程竞争临界资源时,未争夺到的线程需要在一个地方按顺序进行等待,竞争到的线程使用完临界资源释放锁后如果需要再次使用,也需要在该地方进行等待,这就是条件变量。

5. 条件变量函数

5.1 初始化

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

参数:

  • cond:要初始化的条件变量
  • attr:NULL

5.2 销毁

 int pthread_cond_destroy(pthread_cond_t *cond)

5.3 等待条件满足

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

参数:

  • cond:要在这个条件变量上等待
  • mutex:互斥量

✨为什么 pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有⼀个线程,条件不满足,⼀直等下去都不会满足,所以必须要有⼀个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以⼀定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

简单来说就是线程在条件变量下等待时一定在临界资源内,当唤醒时一定需要重新持有锁,这样才能保护公共资源。

5.4 唤醒等待

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

测试代码如下:

#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void *active(void *arg)
{std::string name = static_cast<const char *>(arg);while (true){pthread_mutex_lock(&mutex);// 没有对于资源是否就绪的判定pthread_cond_wait(&cond, &mutex); // mutex??printf("%s is active!\n", name.c_str());pthread_mutex_unlock(&mutex);}
}int main()
{pthread_t tid1, tid2, tid3;pthread_create(&tid1, nullptr, active, (void *)"thread-1");pthread_create(&tid2, nullptr, active, (void *)"thread-2");pthread_create(&tid3, nullptr, active, (void *)"thread-3");sleep(1);printf("Main thread ctrl begin...\n");while (true){printf("main wakeup thread...\n");pthread_cond_signal(&cond);//唤醒单个线程//pthread_cond_broadcast(&cond);//唤醒所有线程sleep(1);}pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);
}
  • 使用条件变量一次唤醒单个线程:

在这里插入图片描述

  • 使用条件变量一次唤醒所有线程:

在这里插入图片描述

5.5 条件变量封装

#pragma once#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"namespace CondModule
{using namespace MutexModule;class Cond{public:Cond(){int n = ::pthread_cond_init(&_cond, nullptr);if(n!=0)std::perror("Cond初始化失败...");}void Wait(Mutex &mutex) {int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());if(n!=0)std::perror("cond wait失败...");}void Notify(){int n = ::pthread_cond_signal(&_cond);if(n!=0)std::perror("notify失败...");}void NotifyAll(){int n = ::pthread_cond_broadcast(&_cond);if(n!=0)std::perror("notifyall失败...");}~Cond(){int n = ::pthread_cond_destroy(&_cond);if(n!=0)std::perror("cond销毁失败...");}private:pthread_cond_t _cond;};
}

为了让条件变量更具有通⽤性,建议封装的时候,不要在Cond类内部引⽤对应的封装互斥量,要不然后⾯组合的时候,会因为代码耦合的问题难以初始化,因为⼀般⽽⾔Mutex和Cond基本是⼀起创建的。

5.6 条件变量使用规范

• 等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

6. 生产者消费者模型

  • 为何要使用生产者消费者模型
    生产者消费者模式就是通过⼀个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于⼀个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是⽤来给生产者和消费者解耦的。

  • 生产者消费者模型优点
    • 解耦
    • 支持并发
    • 支持忙闲不均

在这里插入图片描述

生产消费者模型可以总结为321原则;3种关系:生产者与生产者,生产者与消费者,消费者与消费者;2种角色:生产者与消费者;1个交易场所。

基于BlockingQueue的生产者消费者模型

  在多线程编程中阻塞队列(Blocking Queue)是⼀种常⽤于实现⽣产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列⾥存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞):
在这里插入图片描述

代码如下:

#pragma once#include <iostream>
#include <queue>
#include <pthread.h>
#include "Mutex.hpp"
#include "Cond.hpp"namespace BlockQueueModule
{using namespace MutexModule;using namespace CondModule;static const int gcap = 10;template <typename T>class BlockQueue{private:bool IsFull(){return _q.size() == _cap;}bool IsEmpty(){return _q.size() == 0;}public:BlockQueue(int cap = gcap): _cap(cap),_cwait_num(0),_pwait_num(0){}void Equeue(const T &in) // 生产者{LockGuard lockguard(_mutex);while(IsFull())//必须得是while{std::cout<<"生产者线程进入等待..."<<std::endl;++_pwait_num;_productor_cond.Wait(_mutex);//走到这里线程已经被唤醒,所以要--std::cout<<"生产者线程被唤醒..."<<std::endl;--_pwait_num;}//现在可以生产数据_q.push(in);// 肯定有数据!if (_cwait_num)//如果消费者那边有线程在等待{_consumer_cond.Notify();}}void Pop(T* out){LockGuard lockguard(_mutex);while(IsEmpty()){std::cout<<"消费者线程进入等待..."<<std::endl;++_cwait_num;_consumer_cond.Wait(_mutex);//线程被唤醒&&重新申请并持有锁(它会在临界区内醒来!)std::cout<<"消费者线程被唤醒..."<<std::endl;--_cwait_num;}//现在可以消费数据*out = _q.front();_q.pop();//肯定有空位给消费者生产if(_pwait_num)//如果生产者那边有线程在等待{_productor_cond.Notify();}}~BlockQueue(){}private:std::queue<T> _q;int _cap;Mutex _mutex;Cond _productor_cond;Cond _consumer_cond;int _cwait_num;int _pwait_num;};
}

这里我们使用了之前封装的锁与条件变量

  • 单⽣产,单消费:
#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>using namespace BlockQueueModule;void *Consumer(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while(true){int data;// 1. 从bq拿到数据bq->Pop(&data);// 2.做处理printf("Consumer, 消费了一个数据: %d\n", data);}
}void *Productor(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);int data = 0;while (true){sleep(2);bq->Equeue(data);printf("producter 生产了一个数据: %d\n", data);data++;}
}int main()
{// 交易场所,不仅仅可以用来进行传递数据// 传递任务!!!v1: 对象 v2BlockQueue<int> *bq = new BlockQueue<int>(5); // 共享资源 -> 临界资源// 单生产,单消费pthread_t c1, p1; pthread_create(&c1, nullptr, Consumer, bq);pthread_create(&p1, nullptr, Productor, bq);pthread_join(c1, nullptr);pthread_join(p1, nullptr);delete bq;return 0;
}

结果如下:

在这里插入图片描述

  • 多生产,多消费:
#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>using namespace BlockQueueModule;void *Consumer(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);while(true){int data;// 1. 从bq拿到数据bq->Pop(&data);// 2.做处理printf("Consumer, 消费了一个数据: %d\n", data);}
}void *Productor(void *args)
{BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);int data = 0;while (true){sleep(2);bq->Equeue(data);printf("producter 生产了一个数据: %d\n", data);data++;}
}int main()
{// 交易场所,不仅仅可以用来进行传递数据// 传递任务!!!v1: 对象 v2BlockQueue<int> *bq = new BlockQueue<int>(5); // 共享资源 -> 临界资源pthread_t c1, p1,c2,p2, p3;pthread_create(&c1, nullptr, Consumer, bq);pthread_create(&c2, nullptr, Consumer, bq);pthread_create(&p1, nullptr, Productor, bq);pthread_create(&p2, nullptr, Productor, bq);pthread_create(&p3, nullptr, Productor, bq);pthread_join(c1, nullptr);pthread_join(c2, nullptr);pthread_join(p1, nullptr);pthread_join(p2, nullptr);pthread_join(p3, nullptr);delete bq;return 0;
}

结果如下:

在这里插入图片描述

7. 结语

  以上就是有关线程互斥与同步有关的内容啦,线程互斥指的是多个线程访问公共资源,保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用;线程同步指的是在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。以上就是今天所有的内容啦~ 完结撒花~ 🥳🎉🎉

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

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

相关文章

【数据结构】二叉树

二叉树 1. 树型结构&#xff08;了解&#xff09;1.1 概念1.2 概念&#xff08;重要&#xff09;1.3 树的表示形式&#xff08;了解&#xff09;1.4 树的应用 2. 二叉树&#xff08;重点&#xff09;2.1 概念2.2 两种特殊的二叉树2.3 二叉树的性质2.4 二叉树的存储2.5 二叉树的…

1.五子棋对弈python解法——2024年省赛蓝桥杯真题

问题描述 原题传送门&#xff1a;1.五子棋对弈 - 蓝桥云课 "在五子棋的对弈中&#xff0c;友谊的小船说翻就翻&#xff1f;" 不&#xff01;对小蓝和小桥来说&#xff0c;五子棋不仅是棋盘上的较量&#xff0c;更是心与心之间的沟通。这两位挚友秉承着"友谊第…

Origami Agents:AI驱动的销售研究工具,助力B2B销售团队高效增长

在竞争激烈的B2B市场中,销售团队面临着巨大的挑战——如何高效地发现潜在客户并进行精准的外展活动。Origami Agents通过其创新的AI驱动研究工具,正在彻底改变这一过程。本文将深入探讨Origami Agents的产品特性、技术架构及其快速增长背后的成功因素。 一、一句话定位 Ori…

Java---猜数字游戏

本篇文章所实现的是Java经典的猜数字游戏 , 运用简单代码来实现基本功能 目录 一.题目要求 二.游戏准备 三.代码实现 一.题目要求 随机生成一个1-100之间的整数(可以自己设置区间&#xff09;&#xff0c;提示用户猜测&#xff0c;猜大提示"猜大了"&#xff0c;…

NLP深度学习 DAY5:Seq2Seq 模型详解

Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种用于处理输入和输出均为序列任务的深度学习模型。它最初被设计用于机器翻译&#xff0c;但后来广泛应用于其他任务&#xff0c;如文本摘要、对话系统、语音识别、问答系统等。 核心思想 Seq2Seq 模型的目标是将…

数据结构 队列

目录 前言 一&#xff0c;队列的基本知识 二&#xff0c;用数组实现队列 三&#xff0c;用链表实现队列 总结 前言 接下来我们将学习队列的知识&#xff0c;这会让我们了解队列的基本概念和基本的功能 一&#xff0c;队列的基本知识 (Queue) 我们先来研究队列的ADT&#xff0c…

Git 版本控制:基础介绍与常用操作

目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中&#xff0c;开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…

Java 大视界 -- Java 大数据在量子通信安全中的应用探索(69)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

国产碳化硅(SiC)MOSFET模块在电镀电源中全面取代进口IGBT模块

国产碳化硅&#xff08;SiC&#xff09;MOSFET模块在电镀电源中全面取代进口IGBT模块&#xff0c;倾佳电子杨茜分析以下几方面的技术、经济和政策优势&#xff1a; 倾佳电子杨茜致力于推动SiC碳化硅模块在电力电子应用中全面取代IGBT模块&#xff0c;助力电力电子行业自主可控…

linux用户管理

创建用户&#xff1a;useradd &#xff08;创建用户命令的详细使用&#xff1a;如何创建用户-CSDN博客&#xff09; &#xff08;如何创建具有重复uid的用户&#xff1a;如何创建具有重复uid的用户-CSDN博客&#xff09; 删除用户&#xff1a;userdel &#xff08;删除用户命…

【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027

本文涉及知识点 C动态规划 离散化 LeetCode1626. 无矛盾的最佳球队 假设你是球队的经理。对于即将到来的锦标赛&#xff0c;你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。 然而&#xff0c;球队中的矛盾会限制球员的发挥&#xff0c;所以必须选…

【安全测试】测开方向学习遇到的问题记录

【问题一】springboot如何访问静态资源文件 springboot启动根路径位置 F:\untitled05\demo4\src\main\resources\static 例如图片位置存放在F:\untitled05\demo4\src\main\resources\static即可 配置文件配置 spring.web.resources.static-locationsfile:/F:/untitled05/de…

Github 2025-01-25Rust开源项目日报Top10

根据Github Trendings的统计,今日(2025-01-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Vue项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型…

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…

HarmonyOS应用开发快速入门

本节内容将帮助开发者学习如何构建一个全新的HarmonyOS应用&#xff0c;学习使用DevEco Studio创建新项目、使用预览器预览页面、了解基础组件如Image、Text等。 文章目录 一、介绍二、创建一个新项目三、页面结构总览四、自定义文本视图五、创建Image组件 一、介绍 根据本教程…

ICSE‘25 LLM Assistance for Memory Safety

不知道从什么时候开始&#xff0c;各大技术社区&#xff0c;技术群聊流行着 “用Rust重写!” &#xff0c;放一张图(笑死… 这不, 随着大模型技术的流行&#xff0c;大家都在探索如何让大模型自动完成仓库级别(全程序)的代码重构&#xff0c;代码变换&#xff08;Refactor&…

Java实现.env文件读取敏感数据

文章目录 1.common-env-starter模块1.目录结构2.DotenvEnvironmentPostProcessor.java 在${xxx}解析之前执行&#xff0c;提前读取配置3.EnvProperties.java 这里的path只是为了代码提示4.EnvAutoConfiguration.java Env模块自动配置类5.spring.factories 自动配置和注册Enviro…

基于Python的人工智能患者风险评估预测模型构建与应用研究(下)

3.3 模型选择与训练 3.3.1 常见预测模型介绍 在构建患者风险评估模型时,选择合适的预测模型至关重要。不同的模型具有各自的优缺点和适用场景,需要根据医疗数据的特点、风险评估的目标以及计算资源等因素进行综合考虑。以下详细介绍几种常见的预测模型。 逻辑回归(Logisti…

零基础Vue入门4——Vue3基础核心

本节重点&#xff1a; vue3最佳实践 ref reactive computed watch、watchEffect 讲解重点之后下面会带大家开发一个页面&#xff08;表单表格&#xff09;&#xff0c;之后会有一个TodoList的小练习&#xff0c;文末附有小练习的代码参考。跟着练习一定带你可以上手开发vu…

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…