【探索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++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

golang学习笔记——sync.Pool

文章目录 sync.Pool示例sync.Pool数据结构TCP连接池总结参考资料 sync.Pool示例 代码 sync.Pool对外提供的New、Get和Put方法。 var buffers sync.Pool{New: func() interface{} { return new(bytes.Buffer)}, }func GetBuffer() *bytes.Buffer {return buffers.Get().(*byt…

附录C 流水线:基础与中级概念

1. 引言 1.1 什么是流水线&#xff1f; 流水线爱是一种将多条指令重叠执行的实现技术&#xff0c;它利用了一条指令所需的多个操作之间的并行性。&#xff08;指令操作的非原子性和指令类型的多样性&#xff09; 在计算流水线中&#xff0c;每个步骤完成指令的一部分&#x…

UML概扩知识点

UML是一个重要的知识点&#xff0c;考察的频度也很高。我们需要了解的是UML的一系列的图&#xff0c;红框里的是最核心的。 其次是对各种关系有了解&#xff08;红框里的&#xff1a; 依赖关系&#xff0c;关联关系&#xff0c;泛化关系&#xff0c;实现关系&#xff09; UM…

重点车辆安全监测预警技术方案

目录 1.系统架构 2.详细设计 2.1驾驶员信息监控 2.1.1驾驶员基本信息管理 2.1.2人车匹配信息 2.1.3驾驶员在线状态管理 2.2车辆状态信息管理 2.2.1车辆信息管理 2.1.2车辆在路状态管理 2.3重点车辆安全监测预警系统云平台 2.3.1云平台需求分析 2.3.2 设计思想 2.4.…

Idea的Marketplace下载不了插件,idea下不了插件

Idea的Marketplace下载不了插件 解决方案&#xff08;配置代理&#xff09; 附一张成功界面 2.问题复现 3.问题原因和解决方式&#xff1a;下载安装IDEA之后HTTP Proxy没有进行相关配置的问题&#xff0c;解决方式如下 1.首先打开file->setting->Appearance & B…

深入浅出理解kafka存储机制

前言 Kafka 是为了解决大数据的实时日志流而生的, 每天要处理的日志量级在千亿规模。对于日志流的特点主要包括 &#xff1a; 数据实时产生。 海量数据存储与处理。 所以它必然要面临分布式系统遇到的高并发、高可用、高性能等三高问题。 对于 Kafka 的存储需要保证以下几点&a…

《PySpark大数据分析实战》-01.关于数据

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

uni-app应用设置 可以根据手机屏幕旋转进行 (横/竖) 屏切换

首先 我们打开项目的 manifest.json 在左侧导航栏中找到 源码视图 然后找到 app-plus 配置 在下面加上 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏…

计算目标检测和语义分割的PR

需求描述 实际工作中&#xff0c;相比于mAP项目更加关心的是特定阈值下的precision和recall结果&#xff1b;由于本次的GT中除了目标框之外还存在多边形标注&#xff0c;为此&#xff0c;计算IoU的方式从框与框之间变成了mask之间&#xff1b; 本文的代码适用于MMDetection下的…

kafka常见问题处理

1. 如何防⽌消息丢失 在生产者层面&#xff0c;我们有个ack参数确认机制 设置成-1&#xff0c;也就是副本全部同步了leader才发送ack&#xff0c;这样确保leader和副本挂掉只剩一个还能 保证消息不丢失 消费者&#xff1a; 把⾃动提交改成⼿动提交 2. 如何防⽌重复消费 在…

【数据结构】平衡树引入

数据结构-平衡树 前置知识 二叉树二叉树的中序遍历 问题 维护一个数据结构&#xff0c;支持插入元素、删除元素、查询元素的排名、查询排名对应的元素、查询元素的前驱、查询元素的后继等。 BST&#xff08;二叉搜索树&#xff09; 作为一个基本无效&#xff08;很容易卡掉…

【IC验证】perl脚本——分析前/后仿用例回归情况

目录 1 脚本名称 2 脚本使用说明 3 nocare_list文件示例 4 脚本执行方法 5 postsim_result.log文件示例 6 脚本代码 1 脚本名称 post_analysis 2 脚本使用说明 help&#xff1a;打印脚本说明信息 命令&#xff1a;post_analysis help 前/后仿结束后&#xff0c;首先填…

VoxPoser:使用语言模型进行机器人操作的可组合 3D 值图

语言是一种压缩媒介&#xff0c;人们通过它来提炼和传达他们对世界的知识和经验。大型语言模型&#xff08;LLMs&#xff09;已成为一种有前景的方法&#xff0c;通过将世界投影到语言空间中来捕捉这种抽象。虽然这些模型被认为在文本形式中内化了可概括的知识&#xff0c;但如…

C++STL详解+代码分析+典例讲解

vector 的介绍&#xff1a; 1、vector是表示可变大小数组的序列容器。 2、vector就像数组一样&#xff0c;也采用的连续空间来存储元素&#xff0c;这也意味着可以采用下标对vector的元素进行访问。 3、vector与普通数组不同的是&#xff0c;vector的大小是可以动态改变的。 4、…

基于K-means与CNN的遥感影像分类方法

基于K-means与CNN的遥感影像分类 一、引言 1.研究背景 航天遥感技术是一种通过卫星对地观测获取遥感图像信息数据的技术&#xff0c;这些图像数据在各领域都发挥着不可或缺的作用。遥感图像分类主要是根据地面物体电磁波辐射在遥感图像上的特征&#xff0c;判断识别地面物体的属…

10 大 Mac 数据恢复软件深度评测

对于任何依赖计算机获取重要文件&#xff08;无论是个人照片还是重要商业文档&#xff09;的人来说&#xff0c;数据丢失可能是一场噩梦。值得庆幸的是&#xff0c;有多种专门为 Mac 用户提供的数据恢复工具&#xff0c;可以帮助检索丢失或意外删除的文件。在本文中&#xff0c…

基于Python+Selenium+Unittest+PO设计模式

一、什么是PO设计模式&#xff08;Page Object Model&#xff09; 1、Page Object是一种设计模式&#xff0c;它主要体现在对界面交互细节的封装上&#xff0c;使测试用例更专注于业务的操作&#xff0c;从而提高测试用例的可维护性。 2、一般PO设计模式有三层 第一层&#x…

【基于NLP的微博情感分析:从数据爬取到情感洞察】

基于NLP的微博情感分析&#xff1a;从数据爬取到情感洞察 背景数据集技术选型功能实现创新点 今天我将分享一个基于NLP的微博情感分析项目&#xff0c;通过Python技术、NLP模型和Flask框架&#xff0c;对微博数据进行清洗、分词、可视化&#xff0c;并利用NLP和贝叶斯进行情感分…

基于Lucene的全文检索系统的实现与应用

文章目录 一、概念二、引入案例1、数据库搜索2、数据分类3、非结构化数据查询方法1&#xff09; 顺序扫描法(Serial Scanning)2&#xff09;全文检索(Full-text Search) 4、如何实现全文检索 三、Lucene实现全文检索的流程1、索引和搜索流程图2、创建索引1&#xff09;获取原始…

Moco框架的搭建使用

一、前言   之前一直听mock&#xff0c;也大致了解mock的作用&#xff0c;但没有具体去了解过如何用工具或框架实现mock&#xff0c;以及也没有考虑过落实mock&#xff0c;因为在实际的工作中&#xff0c;很少会考虑用mock。最近在学java&#xff0c;刚好了解到moco框架是用于…