【探索Linux】—— 强大的命令行工具 P.21(多线程 | 线程同步 | 条件变量 | 线程安全)

在这里插入图片描述

阅读导航

  • 引言
  • 一、线程同步
    • 1. 竞态条件的概念
    • 2. 线程同步的概念
  • 二、条件变量
    • 1. 条件变量函数
      • ⭕使用前提
      • (1)初始化条件变量
      • (2)等待条件满足
      • (3)唤醒等待
        • pthread_cond_broadcast()
        • pthread_cond_signal()
      • (4)销毁条件变量
    • 2. 条件变量使用规范
      • (1)条件变量的使用流程
      • (2)条件变量的使用注意事项
    • 3. 使用条件变量的示例
  • 三、线程安全
    • 1. 概念
    • 2. 常见的线程不安全的情况
    • 3. 常见的线程安全的情况
    • 4. 可重入与线程安全的关系(八股文)
      • (1)可重入与线程安全的联系
      • (2)可重入与线程安全的区别
  • 温馨提示

引言

在上一篇文章中,我们详细探讨了多线程编程的基础概念,包括线程互斥、互斥锁以及死锁和资源饥饿等问题。我们了解到,在多线程环境下,为了防止数据竞争和保证程序的正确性,需要采用一定的同步机制来协调线程之间的执行顺序。本篇文章将继续深入探讨多线程编程中的另一组关键概念:线程同步、条件变量和线程安全

在这篇文章中,我们将具体介绍线程同步的技术和模式,探讨条件变量的工作原理以及如何在实际编程中正确使用它们来避免竞态条件和提高程序效率。同时,我们还将分析线程安全的概念,并通过示例展示如何编写线程安全的代码,以确保多线程程序的可靠性和稳定性。随着对这些概念的深入理解,我们将能够更加熟练地掌握多线程编程,打造出更加健壮和高效的软件系统。

一、线程同步

1. 竞态条件的概念

竞态条件(Race Condition)是并发编程中的一个重要概念,它指的是程序的输出或行为依赖于事件或线程的时序。在多线程环境中,如果多个线程共享某些数据,并且它们试图同时读写这些数据而没有适当的同步机制来协调这些操作,就可能出现竞态条件

简单来说,当两个或更多的线程访问共享数据,并且至少有一个线程在修改这些数据时,如果线程之间的执行顺序会影响最终的结果,那么就存在竞态条件。由于线程调度通常由操作系统进行,而且具有一定的随机性,因此竞态条件可能导致程序行为不可预测,有时候甚至非常难以复现和调试

竞态条件的一个典型例子是“检查后行动”(check-then-act)操作,其中线程检查某个条件(如资源是否可用),然后基于这个条件采取行动。如果在检查和行动之间的时间窗口内,另一个线程改变了条件(如抢占了资源),那么第一个线程的行动可能基于错误的假设。

另一个常见的竞态条件是“读-改-写”(read-modify-write)操作,这涉及到读取一个变量的值,对其进行修改,然后写回新值。如果两个线程同时执行这样的操作,而且它们的读取和写入操作是交织在一起的,那么最终写回的值可能只反映了其中一个线程的修改,而另一个线程的修改则丢失了。

为了避免竞态条件,我们需要使用线程同步机制,如互斥锁、信号量、条件变量等,来确保在任何时刻只有一个线程能够访问临界区的代码。通过这种方式,可以序列化对共享资源的访问,从而避免不确定的时序和数据冲突,保证程序的正确性和稳定性

2. 线程同步的概念

线程同步是指在多线程环境中,控制不同线程之间的执行顺序,确保它们能够有序地共享资源和协调工作的一系列机制和方法。当多个线程访问共享资源时,如果没有适当的同步,就可能发生竞态条件(Race Condition),导致数据不一致、程序错误甚至崩溃。

为了防止这些问题,线程同步提供了一种方式,使得在任何时刻只有一个线程可以访问到临界区(Critical Section)。临界区是指那些访问共享资源的代码段,这些资源可能是内存、文件或者其他外部状态。通过线程同步,我们可以确保每次只有一个线程可以操作临界区内的共享资源,从而避免非预期的交互和数据冲突

二、条件变量

条件变量是一种同步原语,它用于线程间的通信,使得一个线程能够在某个特定条件不满足时挂起(等待),直到另一个线程更新了这个条件并通知等待的线程。条件变量通常与互斥锁(mutex)一起使用,以避免竞态条件,并确保数据的一致性。

1. 条件变量函数

⭕使用前提

在Linux环境下,使用条件变量相关的函数需要包含<pthread.h>头文件:

#include <pthread.h>

<pthread.h> 头文件中定义了所有与POSIX线程相关的数据类型、函数原型和宏。这包括了条件变量的操作函数、互斥锁的操作函数以及线程创建和控制的函数。

当编译使用了 <pthread.h> 的程序时,通常需要链接线程库,这可以通过在编译命令中添加 -lpthread 选项来实现。例如:

gcc program.c -o program -lpthread

这条命令会编译 program.c 文件,并将POSIX线程库链接到生成的可执行文件 program 中。

(1)初始化条件变量

在POSIX线程(pthreads)库中,条件变量可以通过两种方式进行初始化:

  1. 静态初始化:使用预定义的宏 PTHREAD_COND_INITIALIZER 来初始化条件变量。这是在程序开始执行之前,即编译时期就已经完成的初始化。

    示例代码:

    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    
  2. 动态初始化:使用函数 pthread_cond_init() 在运行时动态地初始化条件变量。这种方式允许你指定条件变量的属性。

    示例代码:

    pthread_cond_t cond;
    int ret = pthread_cond_init(&cond, NULL); // 使用NULL作为属性参数,表示默认属性
    if (ret != 0) 
    {// 错误处理
    }
    

在动态初始化的情况下,如果你想要设置特定的条件变量属性,可以创建一个 pthread_condattr_t 类型的变量,并使用 pthread_condattr_init() 和相关函数来设置所需的属性。之后,将这个属性变量传递给 pthread_cond_init() 函数。

不论是静态还是动态初始化,初始化后的条件变量都处于未信号化的状态,等待被 pthread_cond_signal()pthread_cond_broadcast() 函数唤醒。

(2)等待条件满足

pthread_cond_wait 函数是POSIX线程库中用于等待条件变量的函数。它的作用是阻塞调用线程直到指定的条件变量被信号化。在等待期间,pthread_cond_wait 会自动释放与条件变量相关联的互斥锁,并且在条件变量被信号化后重新获取互斥锁。

pthread_cond_wait函数的原型:

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

参数解释

  • cond:指向需要等待的条件变量的指针。
  • mutex:指向当前线程已锁定的互斥锁的指针,必须在调用pthread_cond_wait之前由线程锁定。

返回值:

  • 如果函数成功,返回0。
  • 如果失败,将返回一个错误码(非零值)。

使用说明

  1. 线程在调用pthread_cond_wait之前,必须确保已经锁定了mutex互斥锁。
  2. 调用pthread_cond_wait后,线程会阻塞,并且mutex互斥锁被自动释放,以允许其他线程操作条件和互斥锁。
  3. 当其他线程对条件变量调用pthread_cond_signalpthread_cond_broadcast时,等待的线程会被唤醒。
  4. 被唤醒的线程在返回前将重新获取mutex互斥锁。这意味着当pthread_cond_wait返回时,线程已经再次锁定了mutex
  5. 因为可能有多个线程在等待同一个条件变量,所以即使线程被唤醒,也不能假设条件已经满足。通常需要在循环中调用pthread_cond_wait来重新检查条件。

示例代码

// 假设已经声明并初始化了cond和mutex
pthread_mutex_lock(&mutex);
while (condition_is_not_met) 
{pthread_cond_wait(&cond, &mutex);
}
// 此时condition_is_met为真,可以执行依赖于该条件的代码
pthread_mutex_unlock(&mutex);

在这个示例中,线程首先锁定互斥锁mutex,然后在一个循环中检查条件是否满足。如果条件不满足,线程调用pthread_cond_wait等待条件变量cond。当条件变量被其他线程信号化时,线程将被唤醒,并在重新获得互斥锁后继续执行。

(3)唤醒等待

pthread_cond_broadcastpthread_cond_signal 函数都是用来唤醒等待特定条件变量的线程。它们的区别在于唤醒等待线程的数量。

pthread_cond_broadcast()

pthread_cond_broadcast 函数唤醒所有等待特定条件变量的线程。如果没有线程在等待,调用此函数不会有任何效果。

函数原型如下

int pthread_cond_broadcast(pthread_cond_t *cond);

参数解释

  • cond:指向需要广播信号的条件变量的指针。

返回值

  • 如果函数成功,返回0。
  • 如果失败,将返回一个错误码(非零值)。
pthread_cond_signal()

pthread_cond_broadcast不同,pthread_cond_signal函数只唤醒一个正在等待特定条件变量的线程。如果有多个线程在等待,系统选择一个线程唤醒。选择哪个线程通常取决于线程调度策略,程序员无法控制。

函数原型如下

int pthread_cond_signal(pthread_cond_t *cond);

参数解释

  • cond:指向需要发送信号的条件变量的指针。

返回值

  • 如果函数成功,返回0。
  • 如果失败,将返回一个错误码(非零值)。

使用说明

  1. 在调用pthread_cond_signalpthread_cond_broadcast之前,通常需要锁定与条件变量相关联的互斥锁。
  2. 调用这些函数后,互斥锁可以被释放,以便唤醒的线程可以继续执行。
  3. 唤醒的线程将尝试重新获取互斥锁,一旦获取成功,它们就可以检查条件是否满足并继续执行。

示例代码

// 假设已经声明并初始化了cond和mutex
pthread_mutex_lock(&mutex);
// 更新条件并可能修改共享资源
condition_met = 1;
// 唤醒所有等待cond的线程
pthread_cond_broadcast(&cond);
// 或者,只唤醒至少一个等待cond的线程
// pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

在这个示例中,线程首先锁定互斥锁mutex,然后更新条件变量相关的条件。之后,使用pthread_cond_broadcastpthread_cond_signal来唤醒等待该条件变量的线程。最后,线程解锁互斥锁。

(4)销毁条件变量

销毁条件变量是指在条件变量不再需要时,释放它所占用的资源。在POSIX线程(pthreads)库中,可以使用 pthread_cond_destroy 函数来销毁一个条件变量。

pthread_cond_destroy 函数的原型

int pthread_cond_destroy(pthread_cond_t *cond);

参数解释

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

返回值

  • 如果函数成功,返回0。
  • 如果失败,将返回一个错误码(非零值)。

使用说明

  1. 在调用 pthread_cond_destroy 之前,必须确保没有线程正在等待或即将等待条件变量。否则,行为是未定义的,并且可能会导致程序崩溃或其他错误。
  2. 通常,在动态初始化的条件变量不再需要时调用 pthread_cond_destroy。对于静态初始化的条件变量,如果没有分配额外的资源,则可以不调用 pthread_cond_destroy
  3. 一旦条件变量被销毁,你应该避免再次使用它,除非它被重新初始化。

示例代码

// 假设 cond 是一个之前已经初始化的条件变量
int ret = pthread_cond_destroy(&cond);
if (ret != 0) 
{// 错误处理
}

在这个示例中,cond 是一个先前已经初始化并且现在不再需要的条件变量。通过调用 pthread_cond_destroy 来销毁它,从而释放可能分配的资源。如果销毁过程中出现错误,可以根据返回的错误码进行相应的错误处理。

2. 条件变量使用规范

条件变量的运行机制基于两个主要操作:等待(wait)和通知(signal/broadcast)

(1)条件变量的使用流程

  1. 等待条件(Waiting for a Condition):

    • 线程首先获取与条件变量关联的互斥锁。
    • 线程检查某个条件是否满足。如果条件不满足,线程将进入等待状态,并且原子地释放互斥锁,这样其他线程就可以获取互斥锁来更改条件。
    • 当条件变量收到通知后,线程被唤醒,重新尝试获取互斥锁。一旦获取到锁,线程将再次检查条件是否满足,以防在等待期间条件发生了变化。
  2. 通知等待线程(Notifying Waiting Threads):

    • 另一个线程在更改了条件后,会获取相同的互斥锁。
    • 在保持互斥锁的情况下,该线程更新条件。
    • 更新完毕后,线程通过条件变量发送通知,表示条件已经改变。通知操作有两种形式:
      1. signal:唤醒至少一个等待该条件变量的线程。
      2. broadcast:唤醒所有等待该条件变量的线程。
  3. 重新检查条件(Rechecking the Condition):

    • 被唤醒的线程在从等待状态返回时需要重新获得之前释放的互斥锁。
    • 一旦锁被重新获得,线程应该再次检查条件,因为在多个线程等待相同条件的情况下,条件可能已经再次变为假。

(2)条件变量的使用注意事项

  • 使用条件变量时,应当始终与互斥锁配合使用,以防止竞态条件。
  • 必须在修改条件之前获取互斥锁,并在修改完毕后释放互斥锁。
  • 在等待条件变量时,程序应该处于循环中检查条件,即使被signalbroadcast唤醒,也应重新检查条件是否真正满足。
  • 条件变量的等待和通知操作必须在同一个互斥锁保护下进行,以确保数据的一致性。

3. 使用条件变量的示例

#include <pthread.h>
#include <stdio.h>// 定义全局的条件变量和互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *thread_function(void *arg) 
{// 获取互斥锁pthread_mutex_lock(&mutex);// 等待条件变量pthread_cond_wait(&cond, &mutex);// 做一些工作...printf("Received signal\n");// 释放互斥锁pthread_mutex_unlock(&mutex);return NULL;
}int main() 
{pthread_t thread_id;// 创建新线程pthread_create(&thread_id, NULL, thread_function, NULL);// 做一些工作...sleep(1); // 等待一段时间,模拟工作// 发送信号给等待的线程pthread_cond_signal(&cond);// 等待线程结束pthread_join(thread_id, NULL);// 清理资源pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}

这个示例中,主线程创建了一个新线程,并通过条件变量发送信号。新线程在接收到信号后开始执行打印操作。

三、线程安全

1. 概念

线程安全指的是当多个线程同时访问某个功能、对象或变量时,系统能够确保这个功能、对象或变量仍然能够如预期般正常工作。具体来说,一个线程安全的程序对于并发访问没有任何副作用,不会出现数据竞争、死锁等问题,可以正确地处理多线程同时访问的情况。

2. 常见的线程不安全的情况

线程不安全的情况通常发生在多个线程并发访问共享资源时,由于缺乏适当的同步或互斥机制,导致出现意外的结果。以下是一些常见的线程不安全的情况:

  1. 竞态条件(Race Conditions):当多个线程试图同时访问和修改共享的数据,而没有足够的同步保护时,会导致竞态条件。这可能导致数据损坏或不一致的结果。

  2. 数据竞争(Data Races):当至少两个线程同时访问相同的内存位置,其中至少一个是写操作时,且没有适当的同步时,就会发生数据竞争。这可能导致未定义的行为和程序崩溃。

  3. 死锁(Deadlock):当两个或多个线程互相持有对方所需的资源,并且在等待对方释放资源时都不释放自己的资源时,就会产生死锁。这将导致多个线程永远无法继续执行。

  4. 活锁(Livelock):类似于死锁,但线程们不断重试某个操作,却始终无法取得进展,导致系统无法正常工作。

  5. 非原子操作:当一个操作需要多个步骤完成,而这些步骤中间被其他线程打断,可能导致数据状态处于不一致的状态。

  6. 资源泄露:当线程在使用完资源后没有正确释放,导致资源泄露,可能最终耗尽系统资源。

  7. 不一致的状态:当多个线程并发修改共享状态时,由于缺乏同步机制,可能导致状态变得不一致,违反程序的预期行为。

以上情况都代表了典型的线程不安全问题,编写多线程程序时需要格外注意避免这些问题的发生。为了解决这些问题,可以使用锁、原子操作、条件变量等同步机制来确保线程安全,以及遵循良好的并发编程实践。

3. 常见的线程安全的情况

  1. 不可变对象(Immutable Objects):不可变对象在创建后无法被修改,因此多个线程同时访问不会引发线程安全问题。例如,Java中的String类就是不可变对象。

  2. 线程本地存储(Thread-Local Storage):每个线程都有自己独立的变量副本,不会被其他线程共享,从而避免了线程安全问题。可以使用ThreadLocal类来实现线程本地存储。

  3. 局部变量(Local Variables):局部变量是在每个线程的栈帧中创建的,每个线程拥有自己的副本,不存在线程安全问题。

  4. 同步容器(Synchronized Containers):某些容器类(如Vector、Hashtable)提供了内部同步机制,可以安全地在多线程环境下使用。这些容器会确保对它们的操作是原子的,并且提供了线程安全的迭代器。

  5. 并发容器(Concurrent Containers):Java中的ConcurrentHashMap、ConcurrentLinkedQueue等并发容器提供了高效的线程安全操作。它们使用了复杂的算法和数据结构来实现高性能的并发访问。

  6. 使用互斥锁(Mutex)或同步机制:通过在多个线程访问共享资源时使用互斥锁、读写锁等同步机制,可以保证线程安全。这样在任意时刻只有一个线程能够访问共享资源。

  7. 原子操作(Atomic Operations):某些编程语言提供了原子操作,这些操作是不可中断的,可以保证在多线程环境下的原子性。例如,Java中的AtomicInteger类提供了原子操作的整型变量。

  8. 使用并发编程库和框架:一些现代编程语言和框架提供了丰富的并发编程工具和库,如Java中的java.util.concurrent包,可以更方便地实现线程安全。

4. 可重入与线程安全的关系(八股文)

(1)可重入与线程安全的联系

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

(2)可重入与线程安全的区别

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

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

windows安装protoc、protoc-gen-go、protoc-gen-go-grpc

文章目录 一、 protoc二、protoc-gen-go三、protoc-gen-go-grpc 一、 protoc 1&#xff0c;下载&#xff1a;https://github.com/google/protobuf/releases 下载对应的protoc&#xff0c;注意选择windows 2&#xff0c;下好之后解压就行&#xff0c;然后把bin目录加入到环境…

JAVA多线程

线程是相当于独立的&#xff0c;在线程中的也是句不变量&#xff0c;除非i将变量定义一在类中或者调用其他类中的方法&#xff0c;来实现公用。 多线程的创建&#xff1a;有两种方案进行创建多线程 Thread对象提供的多线程(无返回值结果void)&#xff1a; main方法默认是一条主…

【出现模块node_modules里面包找不到】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 一、出现的问题二、解决办法三、其它可供参考 一、出现的问题 在本地运行 npm run docs:dev之后&#xff0c;出现 Error [ERR_MODULE_NOT_FOUND]: Cannot find package Z:\Blog\docs\node_modules\htmlparser2\ imported from Z:\Blo…

坚鹏:中国建设银行商业银行场景生态搭建与GBC联动培训

中国建设银行股份有限公司是一家中国领先的大型商业银行&#xff0c;总部设在北京&#xff0c;其前身中国人民建设银行成立于1954年10月。2005年10月在香港联合交易所挂牌上市&#xff0c;2007年9月在上海证券交易所挂牌上市。建设银行为客户提供公司金融业务、个人金融业务、资…

【MATLAB】基于EMD分解的信号去噪算法(基础版)

代码操作 【MATLAB】基于EMD分解的信号去噪算法&#xff08;基础版&#xff09; 代码的主要内容 基于EMD&#xff08;经验模态分解&#xff09;的信号去噪算法通常可以结合相关系数、信号的熵值或者方差贡献率来完成去噪处理。这些指标可以用于确定阈值&#xff0c;从而对信号…

MVC、MVP、MVVM模式的区别

前言&#xff1a;这三个表现层框架设计模式是依次进化而形成MVC—>MVP—>MVVM。在以前传统的开发模式当中即MVC模式&#xff0c;前端人员只负责Model&#xff08;数据库&#xff09;、 View&#xff08;视图&#xff09;和 Controller /Presenter/ViewModel&#xff08;控…

地址栏不安全提示

在使用浏览器时访问网站的时候&#xff0c;我们可能会遇到地址栏提示不安全的情况。这种情况通常都是是由于未安装有效SSL证书或者网站SSL证书过期等原因导致的。本文将介绍如何处理地址栏提示不安全的问题&#xff0c;以确保我们的上网安全。 1&#xff0c;缺少SSL证书&#x…

唱响主旋律——建行江门市分行推动服务实体经济高质量发展

建行江门市分行主动对接当地战略部署&#xff0c;在侨乡热土踏歌而行&#xff0c;全力当好服务实体经济的主力军和维护金融稳定的压舱石&#xff0c;在助力再造一个现代化新江门上贡献建行力量。 输血实体 为实体经济服务是金融的天职。建行江门市分行积极发挥在重大基建领域…

[oeasy]python0002_终端_CLI_GUI_编程环境_游戏_真实_元宇宙

回忆 上次 了解了 python 语言的特点 历史悠久功能强大深受好评已成趋势 3大主流操作系统 macwindowslinux 我们 选择 linux 作为基础系统 为什么选择 黑乎乎的命令行界面呢&#xff1f;&#x1f914; GUI vs CLI 个人电脑 用图标和菜单组成 图形界面(GUI) Graphic User I…

tamcat乱码

学习springmvc时tamcat乱码 ①、启动时tomcat控制台乱码 解决方法是&#xff1a;1、先把idea设置里的默认字节码改成utf-8 ​ 2、把idea显示编码改成utf-8&#xff0c;在末尾加上&#xff08; -Dfile.encodingUTF-8&#xff09; ​ 3、最后重启idea 加上这个 -Dfile.encodingU…

异步回调模式

异步回调 所谓异步回调&#xff0c;本质上就是多线程中线程的通信&#xff0c;如今很多业务系统中&#xff0c;某个业务或者功能调用多个外部接口&#xff0c;通常这种调用就是异步的调用。如何得到这些异步调用的结果自然也就很重要了。 Callable、Future、FutureTask publi…

【EI会议征稿中】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)

第三届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2024&#xff09; 2024 3rd International Conference on Cyber Security, Artificial Intelligence and Digital Economy 第二届网络安全、人工智能与数字经济国际学术会议&#xff08;CSAIDE 2023&…

Shell数组函数:数组——数组和循环(四)

使用数组统计&#xff0c;用户shell的类型和数量 一、脚本编辑 [root192 ~]# vim shell.sh #!/bin/bash declare -A shells while read ii dotypeecho $ii | awk -F: {print $7}let shells[$type] done < /etc/passwdfor i in ${!shells[]} doecho "$i: ${shells[$i]…

SpringSecurity6 | 自定义登录页面

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Java从入门到精通 ✨特色专栏&#xf…

SAP UI5 walkthrough step7 JSON Model

这个章节&#xff0c;帮助我们理解MVC架构中的M 我们将会在APP中新增一个输入框&#xff0c;并将输入的值绑定到model&#xff0c;然后将其作为描述&#xff0c;直接显示在输入框的右边 首先修改App.controllers.js webapp/controller/App.controller.js sap.ui.define([&…

JAVA使用POI向doc加入图片

JAVA使用POI向doc加入图片 前言 刚来一个需求需要导出一个word文档&#xff0c;文档内是系统某个界面的各种数据图表&#xff0c;以图片的方式插入后导出。一番查阅资料于是乎着手开始编写简化demo,有关参考poi的文档查阅 Apache POI Word(docx) 入门示例教程 网上大多数是XXX…

电商平台商品销量API接口,30天销量API接口接口超详细接入方案说明

电商平台商品销量API接口的作用主要是帮助开发者获取电商平台上的商品销量信息。通过这个接口&#xff0c;开发者可以在自己的应用或网站中实时获取商品的销量数据&#xff0c;以便进行销售分析、库存管理、市场预测等操作。 具体来说&#xff0c;电商平台商品销量API接口的使…

Unity 下载网络图片的方法,并把图片赋值给UI和物体的方法

Unity 下载网络图片的方法&#xff0c;可使用WWW类或UnityWebRequest类&#xff0c;其中UnityWebRequest是新版的方法。 通常我们下载图片都会转成Texture&#xff0c;然后赋值给UI或者物体。 具体实现方法&#xff1a; using System.Collections; using System.Collections…

3D模型制作木质纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 本文将讲解如何使用GLTF 编辑器 -NSDT 在线材质编辑工具为3D模型设置…

朴素贝叶斯 朴素贝叶斯原理

朴素贝叶斯 朴素贝叶斯原理 判别模型和生成模型 监督学习方法又分生成方法 (Generative approach) 和判别方法 (Discriminative approach)所学到的模型分别称为生成模型 (Generative Model) 和判别模型 (Discriminative Model)。 朴素贝叶斯原理 朴素贝叶斯法是典型的生成学习…