c++多线程详解thread mutex atomic condition_variable future

news2024/11/6 9:58:01

c++多线程详解

文章目录

  • c++多线程详解
    • 一. thread
      • thread
        • 创建
        • join
        • detach
      • this_thread
        • get_id
        • yieid
        • sleep_for
        • sleep_until
    • 二. mutex
      • mutex
      • lock_guard
      • unique_lock
    • 三. atomic
      • atomic
      • atomic_flag
    • 四. condition_variable / condition_variable_any
      • wait
      • wait_for
      • wait_until
      • notify_one
      • notify_all
    • 五. future
      • future
      • shared_future
      • promise
      • packaged_task

一. thread

头文件

#include <thread>

thread

创建

线程的创建很简单,只需要将对应的函数添加到线程当中即可
常见以下几种创建方式:

void thread_func() 
{
    std::cout << "t1" << std::endl;
}
std::thread th1(thread_func); //没有参数
void thread_func(int x) 
{
    std::cout << "t2:" << x << std::endl;
}
std::thread th2(thread_func,100);//带参数
void thread_func(double x) 
{
    while (1) 
    {
        std::cout << "t3:" << x << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}
std::thread (thread_func, 100).detach();//没有线程名字

demo:

#include <iostream>  
#include <thread>
#include <chrono>

void test_t1() 
{
    std::cout << "t1" << std::endl;
}

void test_t2(int x) 
{
    std::cout << "t2:" << x << std::endl;
}

void test_t3(double x) 
{
    while (1) 
    {
        std::cout << "t3:" << x << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
}

int main() {
    std::thread th1(test_t1);
    std::thread th2(test_t2,100);
    std::thread (test_t3, 100).detach();
    th1.join();
    th2.join();
    std::cout << "end" << std::endl;
}

join
当线程启动后,在该线程销毁前,确定以join的方式等待线程执行结束。  
join方式:即等待模式,等待该线程结束,才会继续往下执行。  
通常搭配joinable()判断该线程是否处于可等待的状态(如果线程已经结束或者线程被detach了则无法join)。

demo:

#include <iostream>  
#include <thread>
#include <chrono>
void test_t1() 
{
    std::cout << "线程开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "5秒之后" << std::endl;
}

int main() {
    std::thread th1(test_t1);
    if(th1.joinable()) 
    {
        th1.join();
    }
    std::cout << "end" << std::endl;
}
detach
detach方式:即分离模式,该线程自主在后台运行,当前的代码继续往下执行,不等待该线程结束。
#include <iostream>  
#include <thread>
#include <chrono>
void test_t1() 
{
    std::cout << "线程开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "5秒之后" << std::endl;
}

int main() {
    std::thread th1(test_t1);
    th1.detach();
    std::cout << "end" << std::endl; //这里不会等待线程直接运行结束
}

this_thread

get_id

获取线程id

std::this_thread::get_id()	获取线程id
#include <iostream>  
#include <thread>
#include <chrono>
void test_t1() 
{
    std::cout << "thread_id:" << std::this_thread::get_id() << std::endl;
    std::cout << "线程开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "5秒之后" << std::endl;
}

int main() {
    std::thread th1(test_t1);
    th1.join();
    std::cout << "end" << std::endl;
}
yieid

让当前线程主动放弃 CPU 的执行权

注意:

  • 这里并不是终结该线程的意思。
  • 当调用 std::this_thread::yield() 时,当前线程会向调度器请求放弃其当前的时间片。调度器可以选择将 CPU 分配给其他可运行的线程。
  • 这在某些情况下是有用的,尤其是在竞争条件下,可以减少当前线程的 CPU 占用,允许其他线程执行。
  • 在需要频繁检查某个条件的循环中,如果当前线程没有条件满足时,调用 yield() 可以使其他线程有机会运行。这有助于提高程序的响应性,尤其是在多线程环境下。
  • 在一些实时系统中,使用 yield() 可以帮助避免优先级反转问题,因为它允许较低优先级的线程让出执行权,从而使较高优先级的线程能尽快运行。
  • std::this_thread::yield() 的行为依赖于底层操作系统的线程调度策略,不同操作系统可能有不同的实现。
  • 过度使用 yield() 可能导致性能下降,因为频繁让出 CPU 时间可能导致线程切换的开销大于潜在的收益。
#include <iostream>  
#include <thread>
#include <chrono>

void test_t1() 
{
    std::cout << "线程t1开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "t1线程5秒之后" << std::endl;
    std::this_thread::yield();//提示操作系统可以调度其他线程,如果没有其他可运行的线程或当前线程仍然被调度器选中,它将继续执行。
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "t1线程10秒之后" << std::endl;
}

void test_t2()
{
    std::cout << "线程t2开始" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));   //等待5秒
    std::cout << "t2线程5秒之后" << std::endl;
    
}

int main() {
    std::thread th1(test_t1);
    std::thread th2(test_t2);
    th1.join();
    th2.join();
    std::cout << "end" << std::endl;
}
sleep_for

让当前线程休眠指定时间段,可以是 std::chrono::milliseconds、std::chrono::seconds、std::chrono::microseconds 等

std::this_thread::sleep_for(std::chrono::seconds(5));       //等待5秒
std::this_thread::sleep_for(std::chrono::milliseconds(5));  //等待5毫秒
sleep_until

让当前线程暂停执行,直到指定的时间点。
通常使用 std::chrono::steady_clock 或 std::chrono::system_clock 来指定时间点。

示例 1:使用 steady_clock

#include <iostream>
#include <thread>
#include <chrono>
int main() {
    auto wake_time = std::chrono::steady_clock::now() + std::chrono::seconds(3);  // 设定3秒后的时间点
    std::cout << "Sleep!" << std::endl;
    std::this_thread::sleep_until(wake_time);  // 休眠直到设定的时间点
    std::cout << "Awake!" << std::endl;
    return 0;
}

示例 2:使用 system_clock

#include <iostream>
#include <thread>
#include <chrono>
int main() {
    auto wake_time = std::chrono::system_clock::now() + std::chrono::seconds(3);  // 设定3秒后的时间点
    std::cout << "Sleep!" << std::endl;
    std::this_thread::sleep_until(wake_time);  // 休眠直到设定的时间点
    std::cout << "Awake!" << std::endl;
    return 0;
}

二. mutex

#include <mutex>    //头文件   

std::mutex 是 C++11 标准库中提供的一个同步原语,用于保护共享数据的互斥访问,防止多个线程同时访问同一资源,从而导致数据竞争和不一致的状态。

mutex

  1. 基本概念
  • 互斥锁:std::mutex 提供了一种互斥机制,使得在任何时刻只有一个线程可以拥有锁,从而安全地访问共享资源。
  • 锁的状态:std::mutex
    有两种状态:锁定(locked)和未锁定(unlocked)。当一个线程锁定一个互斥量后,其他尝试锁定该互斥量的线程将被阻塞,直到第一个线程释放锁。
  1. 常用成员函数
  • lock():请求锁定互斥量。如果互斥量已经被其他线程锁定,则调用线程会阻塞,直到锁被释放。
  • unlock():释放互斥量。如果调用线程没有持有该锁,则会引发未定义行为。
  • try_lock():尝试锁定互斥量。如果成功,则返回 true;如果互斥量已被锁定,则返回 false,并且不会阻塞线程。

注意: 在使用 lock() 和 unlock() 时,如果在 lock() 和 unlock() 之间发生异常,可能会导致互斥量永远被锁定。为了解决这个问题,可以使用 std::lock_guard 或 std::unique_lock,它们会在作用域结束时自动释放锁。

示例:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx;             // 创建一个互斥量
int shared_counter = 0;     // 共享资源
void increment_counter(int id) {
    for (int i = 0; i < 10; ++i) {
        mtx.lock();         // 请求锁定
        std::cout << "Thread " << id << " add.\n";
        ++shared_counter;   // 修改共享资源
        mtx.unlock();       // 释放锁
    }
    std::cout << "Thread " << id << " finished incrementing.\n";
}

int main() {
    const int num_threads = 10;
    std::vector<std::thread> threads;
    // 创建多个线程
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(increment_counter, i);
    }
    // 等待所有线程完成
    for (auto& th : threads) {
        th.join();
    }
    std::cout << "Final counter value: " << shared_counter << "\n";
    return 0;
}

lock_guard

使用 std::lock_guard 可以确保在作用域结束时自动释放锁,提供更好的异常安全性。

特点:

  • RAII:std::lock_guard 使用 RAII 原则,即资源的获取与释放在对象的生命周期内自动管理。创建 std::lock_guard 对象时,它会自动锁定给定的互斥量,而在对象析构时(即作用域结束时),它会自动释放锁。
  • 构造与析构:std::lock_guard 的构造函数会调用互斥量的 lock() 方法,而析构函数会调用 unlock() 方法。这确保了即使在发生异常时,也能正确释放锁。
  • std::lock_guard 禁用拷贝构造和移动构造,以避免复制对象时误解锁或重复锁定。
  • 不能中途解锁,必须等作用域结束才解锁

示例:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
std::mutex mtx;             // 创建一个互斥量
int shared_counter = 0;     // 共享资源

void increment_counter_safe(int id) {
    for (int i = 0; i < 10; ++i) {
        std::lock_guard<std::mutex> lock(mtx); // 自动管理锁
        std::cout << "Thread " << id << " add.\n";
        ++shared_counter;
    }
    std::cout << "Thread " << id << " finished incrementing.\n";
}

int main() {
    const int num_threads = 10;
    std::vector<std::thread> threads;
    // 创建多个线程
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(increment_counter_safe, i);
    }
    // 等待所有线程完成
    for (auto& th : threads) {
        th.join();
    }
    std::cout << "Final counter value: " << shared_counter << "\n";
    return 0;
}

unique_lock

std::unique_lock 是 C++11 中引入的一个灵活的互斥量管理工具,属于 RAII(Resource Acquisition Is Initialization)风格的锁管理类。与 std::lock_guard 不同,std::unique_lock 提供了更多的功能和灵活性,适用于需要更复杂锁管理场景的情况。

  1. 基本概念
  • RAII 原则:与 std::lock_guard 一样,std::unique_lock 通过对象的生命周期管理互斥量的锁定和解锁。创建 std::unique_lock 对象时,它会自动锁定给定的互斥量,而在对象析构时会自动释放锁。
  • 灵活性:std::unique_lock 提供了可延迟锁定、手动锁定和解锁的能力,还支持锁的转移等高级特性。
  1. 构造与析构
  • 构造函数:可以在创建时锁定互斥量,也可以选择不立即锁定。可以通过 std::defer_lock 参数实现延迟锁定。
  • 析构函数:在 std::unique_lock 对象销毁时,自动释放互斥量的锁。
  1. 常用成员函数
  • lock():手动锁定互斥量。
  • unlock():手动解锁互斥量。
  • try_lock():尝试锁定互斥量,如果成功返回 true,否则返回 false。
  • release():释放对互斥量的控制,将锁的所有权转移给调用者。之后该 unique_lock 对象将不再负责解锁。
  • swap():交换两个 std::unique_lock 对象的状态。
  1. 注意事项
  • 可延迟锁定:std::unique_lock 可以通过 std::defer_lock 参数构造,在需要时再手动调用 lock(),适合复杂的锁定逻辑。
  • 手动解锁:在某些情况下,可能需要在一个锁的作用域内解锁,以便在该作用域中进行其他操作。可以通过调用 unlock() 实现。
  • 异常安全性:使用 std::unique_lock 也能提高异常安全性,因为即使在函数中出现异常,互斥量仍能被正确解锁。
  1. 与 std::lock_guard 的比较
  • 灵活性:std::unique_lock 比 std::lock_guard 更灵活,支持手动锁定、解锁和延迟锁定。
  • 锁的转移:std::unique_lock 可以通过 release() 转移锁的拥有权,而 std::lock_guard 不支持这一特性。
  1. 总结
    std::unique_lock 是一个功能强大的互斥量管理工具,适用于需要复杂锁管理的场景。通过灵活的锁定和解锁策略,可以在多线程程序中有效地保护共享资源,提高代码的安全性和可维护性。使用 std::unique_lock 能够更好地满足多样化的线程同步需求。

延时锁定的示例:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mtx; // 创建一个互斥量
void thread_function() {
    // 使用 std::defer_lock 创建一个未锁定的 unique_lock
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 模拟一些工作
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    // 根据条件决定何时锁定
    lock.lock(); // 手动锁定
    std::cout << "Thread has acquired the lock.\n";
    // 模拟一些工作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    // 自动解锁,当 lock 的作用域结束时
    std::cout << "Thread is releasing the lock.\n";
}

int main() {
    // 创建多个线程
    std::thread t1(thread_function);
    std::thread t2(thread_function);
    // 等待线程完成
    t1.join();
    t2.join();
    return 0;
}

三. atomic

#include <atomic>  //头文件atomic

std::atomic 是 C++11 中引入的一个重要特性,提供了原子操作的支持,使得多线程编程变得更加安全和高效。原子操作是指在多个线程中执行时不会被中断的操作,这意味着在执行时要么完全成功,要么完全失败,不会出现中间状态。

atomic

  1. 基本概念
    原子性:确保操作在多个线程中是不可分割的,其他线程无法在操作执行时观察到中间状态。
    内存序:std::atomic 允许你控制内存操作的顺序,提供了多种内存序模型,如顺序一致性、释放-获取等。
  2. std::atomic 的基本用法
  • 2.1. 声明和初始化
    你可以使用 std::atomic 声明基本类型的原子变量。例如:
#include <atomic>
std::atomic<int> counter(0);

这里,counter 是一个原子整数,初始值为 0。

  • 2.2 原子操作
    std::atomic 提供了一系列的原子操作,包括读取、写入和修改。常用的方法有:

加载和存储:

int value = counter.load(); // 原子读取
counter.store(10);           // 原子写入

原子增加和减少:

counter.fetch_add(1); // 原子增加 1,返回旧值
counter.fetch_sub(1); // 原子减少 1,返回旧值

比较并交换(CAS):

int expected = 0;
int desired = 1;
if (counter.compare_exchange_strong(expected, desired)) {
    // 如果 counter 当前值为 expected,则将其设置为 desired
}
  1. 原子类型的分类
    C++11 提供了多种类型的原子变量,包括:

     整数类型:std::atomic<int>,std::atomic<unsigned int>,std::atomic<long>,等。
     指针类型:std::atomic<T*>,用于指向对象的指针。
     布尔类型:std::atomic<bool>,用于表示布尔值。
    
  2. 内存序模型
    std::atomic 提供了多种内存序选项,允许开发者控制操作的可见性和顺序。常用的内存序包括:

     顺序一致性 (std::memory_order_seq_cst):默认内存序,提供全局顺序一致性。
     释放-获取 (std::memory_order_release 和 std::memory_order_acquire):确保在释放和获取操作之间的依赖关系。
     松散顺序 (std::memory_order_relaxed):不提供同步,但保证原子性。
    

使用示例:

counter.store(10, std::memory_order_relaxed); // 使用松散顺序存储
int value = counter.load(std::memory_order_acquire); // 获取值时使用获取顺序

示例:

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

std::atomic<int> counter(0);
void increment_counter(int n) {
    for (int i = 0; i < n; ++i) {
        counter.fetch_add(1); // 原子增加
    }
}

int main() {
    const int num_threads = 10;
    const int increments_per_thread = 1000;
    std::vector<std::thread> threads;
    // 启动多个线程
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back(increment_counter, increments_per_thread);
    }
    // 等待所有线程完成
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "Final counter value: " << counter.load() << std::endl; // 输出最终计数
    return 0;
}

atomic_flag

atomic_flag 是 C++11 引入的原子类型,专门用于实现简单的标志位操作。它是一个轻量级的原子标志,常用于锁的实现或其他需要线程间同步的场合。下面是对 atomic_flag 的详细解析。

  1. 基本特性
    类型定义:std::atomic_flag 是一个类模板,表示一个原子标志。其大小通常是一个字节,但其具体实现依赖于编译器和平台。
    原子操作:atomic_flag 提供了一组保证原子性的操作,可以在多线程环境中安全地访问和修改。
    无状态:atomic_flag 只能取两个状态:设置(true)和未设置(false)。

  2. 主要操作
    atomic_flag 提供了以下主要的操作:
    test():检查标志的当前状态。如果标志被设置(true),返回 true,否则返回 false。
    test_and_set():原子地设置标志为 true,并返回标志之前的状态。如果标志之前是未设置的(false),则它将返回 false;如果之前是设置的(true),则返回 true。这个操作通常用于实现自旋锁。
    clear():原子地将标志清除为未设置状态(false)。这个操作通常用在锁的释放上。

  3. 示例代码

#include <iostream>
#include <thread>
#include <atomic>
std::atomic_flag flag;
void test_t1() {
    while (flag.test_and_set(std::memory_order_acquire)) {
        // 自旋等待,直到锁可用
    }
    std::cout << "12345" << std::endl; //操作1
    std::cout << "67890" << std::endl; //操作2
    flag.clear(std::memory_order_release);
}

int main() {
    std::thread t1(test_t1);
    std::thread t2(test_t1);
    t1.join();
    t2.join();
}
//如此则不会出现 12345 12345 67890 67890 这种顺序的输出了

四. condition_variable / condition_variable_any

#include <condition_variable> //头文件

std::condition_variable 是 C++11 引入的标准库中的同步原语之一,广泛用于线程间的通信和协调,特别是在多线程程序中,多个线程需要等待某个条件满足时,condition_variable 提供了一种高效的方式来实现线程的等待和通知机制。

  1. 基本概念
    std::condition_variable 是一种线程间同步机制,它允许线程在某个条件满足时进行等待,直到其他线程发出通知信号。其主要用途是让一个线程在等待某个条件时释放互斥锁(mutex),并在条件满足时被其他线程唤醒。
  2. 主要操作
  • 等待操作 (wait): 线程可以通过 condition_variable::wait 函数进入等待状态,直到某个条件满足。
  • 通知操作 (notify_one 或 notify_all): 其他线程可以通过 notify_one 或 notify_all 来唤醒一个或所有正在等待的线程。
  1. 主要方法

     wait
     wait_for
     wait_until
     notify_one()
     notify_all()
    

wait

//这个方法的基本原型如下:
wait(std::unique_lock<std::mutex>& lock)

//或者它的重载版本,可以接受一个条件谓词(Predicate):(推荐)
template< class Predicate >
void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
参数解释
std::unique_lock<std::mutex>& lock:
	这是一个互斥量(mutex)的独占锁,它将在等待期间被自动释放。当线程进入等待状态时,std::unique_lock 会释放 mutex 锁,使得其他线程可以访问共享资源。
	当 wait 方法返回时,std::unique_lock 会重新获取锁,从而保证线程的同步。
Predicate pred(在第二种重载方法中):
	这是一个布尔条件谓词,通常是一个 lambda 函数或者其他可以返回 bool 的函数。
	如果传递了 pred,线程会继续等待,直到 pred() 返回 true。每次被唤醒后,都会重新检查 pred() 的条件是否满足。
	这通常用于避免“虚假唤醒”(spurious wakeups)。

虚假唤醒是指线程在没有调用 notify_one() 或 notify_all() 的情况下被唤醒。这种情况可能会发生,因此在使用 wait 时,最好总是将其放在一个 while 循环中检查条件,确保条件满足后再继续执行。
虚假唤醒通常是由于线程调度机制、操作系统实现或线程库底层的优化策略引起的,是一种不可预测的现象。

对于没有条件谓词的 wait 方法,while 循环通常是这样的:

std::unique_lock<std::mutex> lock(mtx);
while (!condition) {
    cv.wait(lock);  // 如果条件不满足,继续等待
}

使用带条件谓词的 wait 方法,while 循环会自动封装这个检查过程:

std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return condition; });  // 只有条件为 true 时才会返回

一个典型的使用场景是生产者-消费者模型。以下是一个简单的例子:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
const unsigned int max_queue_size = 10;

void producer() {
    for (int i = 0; i < 20; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return data_queue.size() < max_queue_size; });  // 当队列中有10个商品时,条件判断失败,wait生效,同时会解锁
        
        data_queue.push(i); 
        std::cout << "Produced: " << i << std::endl;

        lock.unlock(); //这里手动解锁,如果不手动解锁的话等待过程中消费者的wait依旧处于被锁住状态
        cv.notify_all();  // 唤醒消费者

        std::this_thread::sleep_for(std::chrono::milliseconds(100));//假设每生产一个商品需要等待100ms
    }
}

void consumer() {
    
    for (int i = 0; i < 20; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !data_queue.empty(); });  // 等待队列不为空

        int data = data_queue.front();
        data_queue.pop();
        std::cout << "----Consumed: " << data << std::endl;

        lock.unlock();
        cv.notify_all();  // 唤醒生产者
        std::this_thread::sleep_for(std::chrono::milliseconds(500)); //假设每消费一件商品需要等待500ms
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

wait_for

wait_for(std::unique_lock<std::mutex>& lock, const std::chrono::duration<Rep, Period>& rel_time)

除了等待条件变量外,还可以等待一定的时间,直到条件满足或时间到期。返回值为 std::cv_status,表示等待的状态(成功或超时)。

wait_until

wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& time_point)

等待直到某个时间点。类似于 wait_for,但使用绝对时间来指定超时。

notify_one

notify_one()

唤醒等待条件变量的其中一个线程。如果有多个线程在等待,只有其中一个线程会被唤醒。

notify_all

notify_all()

唤醒所有等待的线程。

五. future

future

shared_future

promise

packaged_task

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

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

相关文章

IT架构管理

目录 总则 IT架构管理目的 明确组织与职责 IT架构管理旨在桥接技术实施与业务需求之间的鸿沟&#xff0c;通过深入理解业务战略和技术能力&#xff0c;推动技术创新以支持业务增长&#xff0c;实现技术投资的最大价值。 设定目标与范围 IT架构管理的首要目的是确立清晰的组织…

Rust项目结构

文章目录 一、module模块1.文件内的module 二、模块化项目结构1.关于module2.各个模块之间互相引用 三、推荐项目结构1.实例 参考 一、module模块 1.文件内的module 关键字&#xff1a;mod 引入模块中的方法 usemod名字&#xff1a;方法名usemod名字.*写全路径 二、模块化…

HiveSQL 中判断字段是否包含某个值的方法

HiveSQL 中判断字段是否包含某个值的方法 在 HiveSQL 中&#xff0c;有时我们需要判断一个字段是否包含某个特定的值。下面将介绍几种常用的方法来实现这个功能。 一、创建示例表并插入数据 首先&#xff0c;我们创建一个名为employee的表&#xff0c;并插入一些示例数据&am…

408——计算机网络(持续更新)

文章目录 一、计算机网络概述1.1 计算机网络的概念1.2 计算机网络体系结构1.3 总结 二、物理层2.1 物理层的基本概念2.2 物理层的基本通信技术2.3 总结 三、数据链路层3.1 数据链路层基础概论3.2 数据链路层的通信协议 一、计算机网络概述 1.1 计算机网络的概念 计算机网络的定…

正反shell反弹的区分

在shell反弹中我们会根据参照物的不同来区分正反shell反弹。 本次我们需要使用win和kali进行实验&#xff1a; 在shell反弹中我们需要在win上面安装netcat&#xff08;瑞士军刀&#xff09;用于可以执行监听指令。 下载指导链接https://blog.csdn.net/qq_40359932/article/d…

CSS的配色

目录 1 十六进制2 CSS中的十六进制2.1 十六进制颜色的基本结构2.2 十六进制颜色的范围2.3 简写形式2.4 透明度 3 CSS的命名颜色4 配色4.1 色轮4.2 互补色4.3 类似色4.4 配色工具 日常在开发小程序中&#xff0c;客户总是希望你的配色是美的&#xff0c;但是美如何定义&#xff…

Java 基于SpringBoot+Vue 的公交智能化系统,附源码、文档

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

qt QFile详解

1、概述 QFile类是Qt框架中用于读取和写入文本和二进制文件资源的I/O工具类。它继承自QFileDevice类&#xff0c;后者又继承自QIODevice类。QFile类提供了一个接口&#xff0c;允许开发者以二进制模式或文本模式对文件进行读写操作。默认情况下&#xff0c;QFile假定文件内容为…

react jsx基本语法,脚手架,父子传参,refs等详解

1&#xff0c;简介 1.1 概念 react是一个渲染html界面的一个js库&#xff0c;类似于vue&#xff0c;但是更加灵活&#xff0c;写法也比较像原生js&#xff0c;之前我们写出一个完成的是分为html&#xff0c;js&#xff0c;css&#xff0c;现在我们使用react库我们把html和js结…

Qt学习笔记第41到50讲

第41讲 UI美化遗留问题解决 如上图所示目前记事本的雏形已现&#xff0c;但是还是有待优化&#xff0c;比如右下角的拖动问题。 解决方法&#xff1a; ①首先修改了Widget类的构造函数。 Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) {ui->s…

Linux(VMware + CentOS )设置固定ip

需求&#xff1a;设置ip为 192.168.88.130 先关闭虚拟机 启动虚拟机 查看当前自动获取的ip 使用 FinalShell 通过 ssh 服务远程登录系统&#xff0c;更换到 root 用户 修改ip配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 重启网卡 systemctl restart network …

CAN总线学习笔记(1、CAN总线定义)

CAN总线学习笔记&#xff08;1、CAN总线定义&#xff09; 江协科技CAN总线入门教程视频学习笔记 CAN特性 两根通信线&#xff08;CAN_H\CAN_L&#xff09;,两根线&#xff0c;无需工地 差分信号&#xff0c;抗干扰能力强 高速CAN&#xff08;ISO11898&#xff09;&#xff…

伍光和《自然地理学》电子书(含考研真题、课后习题、章节题库、模拟试题)

《自然地理学》&#xff08;第4版&#xff09;由伍光和、王乃昂、胡双熙、田连恕、张建明合著&#xff0c;于2018年11月出版。作为普通高等教育“十一五”国家级规划教材&#xff0c;本书不仅适用于高校地球科学各专业的基础课程&#xff0c;还可供环境、生态等有关科研、教学人…

Idea如何推送项目到gitee

第一步&#xff1a;先在你的gitee创建一个仓库 第二步&#xff1a; 点击推送 点击定义远程&#xff0c;将URL换成你仓库的&#xff0c;填好你的用户名和密码 可以看到已经推送到仓库了

AI笔筒操作说明及应用场景

AI笔筒由来&#xff1a; 在快节奏的现代办公环境中&#xff0c;我们一直在寻找既能提升效率、增添便利&#xff0c;又能融入企业文化、展现个人品味的桌面伙伴。为此&#xff0c;我们特推出专为追求卓越、注重细节的您设计的AI笔筒礼品版&#xff0c;它集高科技与实用性于一身…

【C++】内存管理(二):operator new/delete

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的operator new/delete&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 new/delete的底层2 new/delete的底层调用顺序3 delete[ ]调用析构函数的次数…

【工具变量】中国制造2025试点城市数据集(2000-2023年)

数据简介&#xff1a;《中国制造2025》是中国ZF于2015年5月8日印发的一项战略规划&#xff0c;旨在加快制造业的转型升级&#xff0c;提升制造业的质量和效益&#xff0c;实现从制造大国向制造强国的转变。该规划是中国实施制造强国战略的第一个十年行动纲领&#xff0c;明确提…

小菜家教平台(一):基于SpringBoot+Vue打造一站式学习管理系统

前言 现在已经学习了很多与Java相关的知识&#xff0c;但是迟迟没有进行一个完整的实践&#xff08;之前这个项目开发到一半&#xff0c;很多东西没学搁置了&#xff0c;同时原先的项目中也有很多的问题&#xff09;&#xff0c;所以现在准备从零开始做一个基于SpringBootVue的…

算法专题:字符串

目录 1. 最长公共前缀 1.1 算法原理 1.2 算法代码 2. 最长回文子串 2.1 算法原理 2.2 算法代码 3. 二进制求和 3.1 算法原理 3.2 算法代码 4. 字符串相乘 4.1 算法原理 4.2 算法代码 1. 最长公共前缀 . - 力扣&#xff08;LeetCode&#xff09; 1.1 算法原理 有以…

非线性数据结构之图

一、有向图&#xff08;Directed Graph&#xff09; 1. 定义 有向图是一个由顶点&#xff08;节点&#xff09;和有方向的边&#xff08;弧&#xff09;组成的图。在有向图中&#xff0c;每条边都有一个起点和一个终点&#xff0c;表示从一个顶点到另一个顶点的关系。 2. 特…