CppCon 2018 学习:What do you mean “thread-safe“

什么是“线程安全”?

线程安全”指的是一个函数、方法或代码块能够在多个线程同时执行时,不会出现意外的交互或破坏共享数据,能够安全地运行。
POSIX 对线程安全的定义很清楚:

“一个线程安全的函数可以在多个线程中被安全地并发调用,无论是与同一个函数的其他调用,还是与其他线程安全函数的调用。”

这意味着,当多个线程同时调用一个函数时,这个函数应该能够正常工作,而不会导致数据破坏或未定义的行为。

线程安全的例子:

  1. 非线程安全的例子:
    int in[100], out[100];
    void thread1() {memcpy(&out, &in, sizeof(in));
    }
    void thread2() {memcpy(&out, &in, sizeof(in));
    }
    
    • 在这里,thread1thread2 都在并发访问同一个 out 数组。
    • 如果这两个线程同时调用 memcpy,就会发生 数据竞争。即一个线程在将数据复制到 out 时,另一个线程可能正在修改它,导致不可预测的结果。
    • 这个例子 不是线程安全的,因为 out 数组是共享的,并且没有同步机制来防止多个线程同时访问它。
  2. 线程安全的例子:
    void thread1() {int out[100];memcpy(&out, &in, sizeof(in));
    }
    void thread2() {int out[100];memcpy(&out, &in, sizeof(in));
    }
    
    • 在这种情况下,每个线程都使用自己独立的 out 数组。
    • 由于每个线程都有自己的 out 数组,因此不会发生线程间的冲突。
    • 这个代码 是线程安全的,因为每个线程都有自己的内存空间,不会互相干扰。

我们要“安全”避免什么?

在多线程的上下文中,提到“安全”主要是为了避免 竞态条件(race condition)。

什么是 竞态条件

竞态条件是指当系统的行为依赖于事件的顺序或时机,而这个顺序或时机是无法控制的情况下发生的情况。在多线程程序中,当两个或更多的线程同时访问共享资源(如内存或变量),并且至少有一个线程修改了这个资源,就可能发生竞态条件。

竞态条件的例子:

  • 线程 A 和 线程 B 都访问一个共享的计数器。
  • 线程 A 读取计数器的值并将其加 1。
  • 线程 B 也读取了相同的计数器值(在线程 A 写入更新的值之前),然后将其加 1。
  • 最终的计数器值可能没有反映出两个线程的增量,因为两个线程在更新之前都读取了相同的初始值,导致 更新丢失错误的结果

为什么线程安全很重要?

线程安全确保了在多线程环境中,数据的一致性不会被破坏,并且避免了未定义的行为。如果没有适当的线程同步,多个线程可能会相互干扰,导致 数据损坏不预期的行为,甚至 崩溃
总结:

  • 线程安全的代码确保多个线程可以并发执行而不会发生不安全的交互。
  • 它通过在共享数据访问上进行有效的同步管理,避免了竞态条件。
  • 通常,使用 互斥锁原子操作 等同步机制来保证代码线程安全。
    如果你想要更深入的了解如何让代码线程安全,或者有任何问题,欢迎随时提问!

我们要“安全”避免什么?—— 数据竞争(Data Races)

在多线程编程中,数据竞争(data race)是一个常见且严重的问题,它通常会导致不可预测的行为、错误的结果,甚至是程序崩溃。

什么是数据竞争?

根据 C++ 标准,一个程序中如果存在数据竞争,那么它包含了两个潜在的并发冲突操作,其中至少有一个操作是 非原子操作
更具体地说:

数据竞争是指两个表达式的评估存在冲突:

  • 其中一个表达式修改了某个内存位置
  • 另一个表达式在同一时间读取或修改了同一个内存位置。

数据竞争的例子:

  1. 修改共享变量的例子:
    int i = 0;
    void thread1() {++i;  // 增加 i 的值
    }
    void thread2() {std::cout << i;  // 输出 i 的值
    }
    
    • 问题:
      • thread1 在增量操作 i 时,i 的值可能会受到 thread2 读取 i 时的影响。
      • 如果 thread1thread2 同时执行,i 的值可能会因为这两者之间的冲突而变得不确定(例如,thread1 修改了 i,但 thread2 可能读取了它的旧值,或者 i 的增量操作没有被正确执行)。
    • 数据竞争的原因:
      • i 是共享资源,并且没有同步机制(例如,锁、原子操作等)来保护对 i 的并发访问。
    • 解决方案:
      • 使用 原子操作(如 std::atomic)来确保对 i 的访问是原子性的。
      • 或者使用互斥锁(std::mutex)来同步对 i 的访问。
  2. 修改字符串的例子:
    std::string s = "";
    void thread1() {s.append("foo");  // 向字符串中添加 "foo"
    }
    void thread2() {std::cout << s;  // 输出字符串 s
    }
    
    • 问题:
      • thread1 修改了字符串 s,而 thread2 同时尝试读取 s
      • 如果这两个线程同时执行,s 可能在 thread1 修改时发生冲突,导致 字符串内容不一致 或者 程序崩溃
    • 数据竞争的原因:
      • s 是共享资源,且没有任何同步机制来防止线程间的并发访问。
    • 解决方案:
      • 使用 std::mutex 来同步对 s 的访问,确保只有一个线程可以修改或读取 s
      • 或者考虑使用线程安全的数据结构(例如,std::atomic<std::string>,虽然这在 C++ 标准库中并没有直接支持)。

总结:

数据竞争是指两个或多个线程在并发执行时,至少一个线程对共享数据的访问是非原子的,且没有适当的同步机制来确保数据一致性。数据竞争会导致不确定的行为和错误的结果,因此我们需要使用锁、原子操作等同步机制来避免数据竞争。

  • 避免数据竞争的关键点:
    • 确保对共享资源的访问是原子的。
    • 使用合适的同步机制(如 std::mutexstd::atomic)。
    • 尽量避免多个线程同时修改同一共享资源。
      通过这些措施,可以确保代码在并发执行时的正确性和可靠性。

我们要“安全”避免什么?—— API 竞争(API Races)

除了 数据竞争,程序中的另一个常见并发问题是 API 竞争。API 竞争是指在同一个对象上执行并发操作时,该对象的 API 合同(或约定)并不允许这些操作并发执行。

什么是 API 竞争(API Race)?

API 竞争发生在一个程序执行两个并发操作,这两个操作在同一个对象上进行,但该对象的 API 并没有保证这些操作是安全的,或者没有提供适当的同步机制。
简而言之,API 竞争是指多个线程或操作同时在同一个对象上执行时,违反了该对象的使用规则或约定,可能会导致不一致或错误的结果。

API 竞争的例子:

std::string s = "";
void thread1() {s.append("foo");  // 向字符串 s 中添加 "foo"
}
void thread2() {std::cout << s;   // 输出字符串 s
}
问题分析:
  • 在这个例子中,s.append("foo")std::cout << s 是两个操作,分别发生在不同的线程中。
  • 虽然 std::string 类本身是可以用于多个线程,但在并发访问时,std::string 的 API 并没有保证这些操作是线程安全的。
    • thread1 正在修改字符串 s
    • thread2 正在读取字符串 s
  • 这些操作如果同时执行,可能会导致API 竞争,因为 std::string 没有内建机制来同步并发访问。这样会导致以下问题:
    • 字符串的修改和读取之间可能会发生冲突,导致读取到不一致的值。
    • 在某些情况下,可能会发生内存损坏或者崩溃。
API 竞争的原因:
  • std::string 的 API 并没有保证多线程环境下并发修改或读取的安全性。
  • 没有同步机制来保证 append<< 操作不会互相干扰。
解决方案:
  • 使用互斥锁(mutex):使用 std::mutex 来保证在某个时刻,只有一个线程能够访问和修改 s
    std::mutex mtx;
    std::string s = "";
    void thread1() {std::lock_guard<std::mutex> lock(mtx);s.append("foo");
    }
    void thread2() {std::lock_guard<std::mutex> lock(mtx);std::cout << s;
    }
    
  • 使用线程安全的类或 API:如果需要多线程操作共享数据,选择专门设计为线程安全的类,或者自己实现相应的同步机制。

总结:

API 竞争是指在并发执行时,程序操作的对象的 API 合同并没有明确保障多个操作能够安全地并发执行。为了避免 API 竞争,我们需要确保:

  • API 的约定:明确使用对象时,它的 API 是否支持并发操作,或者需要采取额外的同步措施。
  • 同步机制:通过互斥锁、条件变量等同步工具,确保在多线程环境下共享资源的安全访问。
    通过合理的同步机制和正确理解 API 合同,可以有效避免 API 竞争,提高程序的并发安全性。

识别 API 竞争(API Races)

API 竞争发生在多个线程同时操作同一个对象时,而该对象的 API 合同并不支持这种并发操作。识别 API 竞争是确保多线程程序安全的关键部分。下面我们来通过几个例子详细探讨如何识别 API 竞争。

例 1: API 竞争示例 — 在同一对象上调用不同方法

Widget shared_widget;
void thread1() {// 线程1调用 shared_widget 的 foo() 方法shared_widget.foo();
}
void thread2() {// 线程2调用 shared_widget 的 bar() 方法shared_widget.bar();
}
分析:
  • 问题shared_widget 对象被多个线程同时操作,foo()bar() 方法可能对同一个数据进行修改。由于 Widget 类的 API 没有保证这两个方法的线程安全,这可能导致数据竞争(Data Race),比如两个线程同时修改 shared_widget 内部的成员变量,从而导致数据不一致。
  • 解决方案
    • 确保 foo()bar() 方法是线程安全的。
    • 使用互斥锁(std::mutex)保护对 shared_widget 对象的访问,确保一次只有一个线程能够调用这两个方法。

例 2: API 竞争示例 — 通过不同的函数调用同一对象

Widget shared_widget;
void thread1() {Thingy t;t.foo(shared_widget);  // 线程1调用 t.foo(shared_widget)
}
void thread2() {Whatever w;w.bar(shared_widget);  // 线程2调用 w.bar(shared_widget)
}
分析:
  • 问题shared_widget 被两个线程分别传递给 ThingyWhatever 类型的对象,并通过它们调用方法。这种情形依然可能导致 API 竞争,尤其是在 foo()bar() 方法内部执行对 shared_widget 的修改操作时。
  • 解决方案
    • Thingy::foo()Whatever::bar() 方法同步访问 shared_widget,通过互斥锁等同步机制确保同一时刻只有一个线程能访问该对象。
    • 如果方法内部没有修改 shared_widget,那么可以认为是线程兼容的,但仍需要确保没有其他竞争条件。

例 3: 线程安全的设计 — 使用互斥锁避免竞争条件

// 线程安全的 JobRunner 类
class JobRunner {JobSet running_;JobSet done_;std::mutex m_;  // 用于同步的互斥锁void OnJobDone(Job* job) {m_.lock();running_.erase(job);  // 在互斥锁保护下操作 running_ 集合done_.insert(job);    // 在互斥锁保护下操作 done_ 集合m_.unlock();}
};
// 线程安全的 JobSet 类
class JobSet {std::set<Job*> jobs_;std::mutex m_;  // 用于同步的互斥锁void erase(Job* job) {m_.lock();jobs_.erase(job);  // 线程安全地操作 jobs_ 集合m_.unlock();}
};
分析:
  • 问题解决:在这个设计中,JobRunnerJobSet 类通过使用 std::mutex 来同步对共享数据的访问,确保了并发操作的安全性。这样,多个线程可以安全地同时调用 OnJobDone()erase() 方法,而不会引起竞争条件。

例 4: 数据类型的线程安全

int shared_int;
void thread1() {Thingy t;t.foo(shared_int);  // 线程1调用 foo() 方法
}
void thread2() {Whatever w;w.bar(shared_int);  // 线程2调用 bar() 方法
}
void Thingy::foo(int i) {// 对 shared_int 进行修改
}
void Whatever::bar(const int& i) {// 读取 shared_int
}
分析:
  • 问题:如果 Thingy::fooWhatever::bar 方法都对 shared_int 进行修改,或者至少是并发读取,它们之间就可能会发生 API 竞争,尤其在 shared_int 没有进行同步保护的情况下。
  • 解决方案
    • shared_int 进行同步保护,确保同一时刻只有一个线程能够修改或读取它。
    • 可以使用互斥锁来保护对 shared_int 的访问,或者使用 std::atomic 来确保并发操作时的原子性。

例 5: 线程兼容的对象

Widget shared_widget;
void thread1() {Thingy t;t.foo(shared_widget);
}
void Thingy::foo(const Widget& widget) {// 对 widget 进行只读操作,不修改 shared_widget
}
void thread2() {Whatever w;w.bar(shared_widget);
}
void Whatever::bar(const Widget& widget) {// 对 widget 进行只读操作,不修改 shared_widget
}
分析:
  • 问题:如果 shared_widget 是一个线程兼容的类型,并且在 foo()bar() 方法中仅进行读取操作,而不进行修改,则不会发生 API 竞争。因为此时没有线程同时修改 shared_widget,因此不会产生并发修改的问题。
  • 解决方案:确保在多线程环境下,只读访问共享对象,而不进行修改。这样,即使多个线程同时访问该对象,也不会引发竞争条件。

总结:

  1. API 竞争的核心问题:当多个线程在同一时刻访问同一对象,而该对象的 API 合同不支持并发操作时,就会产生 API 竞争。
  2. 如何避免 API 竞争
    • 使用 互斥锁std::mutex)来同步对共享对象的访问。
    • 如果操作是只读的,并且对象类型支持线程兼容(如 std::atomic),则可以避免竞争。
    • 设计线程安全的 API 或类型,确保在多线程环境下可以安全并发执行。
  3. 识别 API 竞争
    • 如果对象类型不是线程安全的,或者操作没有同步机制,便会发生 API 竞争。
    • 如果对象的操作不符合 API 合同(例如不保证线程安全),则需要加锁或其他同步措施。
      通过正确的同步和线程安全设计,可以有效避免 API 竞争,保证多线程程序的安全性和稳定性。

识别 API 竞争(API Races)

API 竞争指的是多个线程在同一时刻并发访问同一对象的情况,导致不可预期的行为,尤其当该对象的 API 合同不支持并发时。正确识别并避免 API 竞争是确保多线程程序正确性和稳定性的关键。以下是通过几个例子进一步理解 API 竞争及其避免方法。

例 1: 线程安全的 LazyStringView

class LazyStringView {const char* data_;mutable std::optional<size_t> size_;mutable std::mutex mu_;
public:size_t size() const {std::scoped_lock lock(mu_);  // 通过 scoped_lock 确保线程安全if (!size_) {size_ = strlen(data_);}return *size_;}
};
分析:
  • 问题LazyStringView 类的 size() 方法需要访问 size_data_ 成员变量,这在多线程环境下是潜在的并发问题源。为了确保线程安全,在 size() 方法中使用了 std::mutex 来加锁,确保每次只有一个线程能修改 size_
  • 解决方案:通过 std::scoped_lock 确保在访问 size_data_ 时是互斥的。这样可以确保即使多个线程同时调用 size(),也不会发生竞争条件。

例 2: 竞争条件示例 — 不同线程调用不同方法

Widget shared_widget;
void thread1() {Thingy t;t.foo(shared_widget);  // 线程1调用 foo(shared_widget)
}
void thread2() {Whatever w;w.bar(shared_widget);  // 线程2调用 bar(shared_widget)
}
分析:
  • 问题:在这个例子中,shared_widget 是一个共享对象,多个线程同时调用不同的方法可能导致竞争条件。假如 foo()bar() 方法都操作了 shared_widget,且这些方法没有进行适当的同步,那么就会发生数据竞争。
  • 解决方案:可以使用互斥锁(std::mutex)对对 shared_widget 的访问进行保护,确保同一时刻只有一个线程能够操作它。

例 3: 不同线程调用相同对象的不同方法

Widget shared_widget;
void thread1() {Thingy t;t.foo(shared_widget);
}
void Thingy::foo(const Widget& widget) {// 对 widget 的操作
}
void thread2() {Whatever w;w.bar(shared_widget);
}
void Whatever::bar(const Widget& widget) {// 对 widget 的操作
}
分析:
  • 问题shared_widget 可能在两个不同线程中同时被传递给 foo()bar() 方法。如果这两个方法都试图访问并修改 shared_widget,且没有适当的同步机制,就会发生竞争条件。
  • 解决方案:确保 foo()bar() 方法在访问 shared_widget 时是线程安全的。可以使用互斥锁来同步访问,或者将 Widget 设计为线程安全类型。

例 4: 线程不安全的函数调用

void Thingy::foo(const Widget&) {static int counter = 0;counter++;  // 修改静态变量,可能导致数据竞争
}
void Whatever::bar(const Widget&) {static int counter = 0;counter++;  // 修改静态变量,可能导致数据竞争
}
分析:
  • 问题foo()bar() 方法都在修改静态变量 counter,如果两个线程同时调用这两个方法,且它们都试图修改 counter,就会发生数据竞争。
  • 解决方案:将静态变量 counter 设计为线程安全的,或者使用互斥锁保护对 counter 的访问,避免数据竞争。

线程安全与线程兼容类型

1. 线程安全的类型:
  • 如果对象的类型本身是线程安全的(例如使用 std::mutex 等机制进行同步),则该对象在多线程环境下是安全的,不能成为 API 竞争的源头。
2. 线程兼容的类型:
  • 如果对象类型是线程兼容的(例如,只进行只读操作,不会改变内部状态),并且没有被并发修改,那么该对象在并发访问下是安全的。
3. 非线程安全类型:
  • 如果一个对象的类型在多线程环境下没有设计为线程安全,且多个线程同时对它进行修改操作,那么就会发生 API 竞争。这种对象必须加以同步或避免并发访问。

总结:

  • 线程安全的类型:如果一个对象的类型是线程安全的(如通过互斥锁保护),它不能成为 API 竞争的源头。
  • 线程兼容的类型:如果对象的状态不会在并发访问中被修改,那么即使多个线程同时访问该对象,也不会发生 API 竞争。
  • 静态变量与线程安全:静态变量在多线程环境中需要特别注意,如果多个线程并发访问并修改它们,可能会导致数据竞争。可以通过互斥锁或其他同步机制来避免这个问题。
    通过合理的设计和同步机制,我们可以有效避免 API 竞争,确保多线程程序的安全性和正确性。

识别 API 竞争条件(API Races)

API 竞争(API Race)指的是在多线程环境中,多个线程并发访问同一对象并试图对其进行操作,导致不一致的结果或不可预测的行为。为避免这些问题,我们需要确保访问共享数据时采取正确的同步机制。下面是一些典型的案例以及如何判断和避免 API 竞争。

如何确保没有 API 竞争条件

一行代码如果要保证没有 API 竞争条件,必须满足以下条件:

  1. 不调用线程敌对函数(thread-hostile functions)。
  2. 所有输入参数必须是活动的(live),即没有被销毁或处于非法状态。
  3. 每个输入对象必须满足下列其中一个条件:
    • 不被其他线程访问
    • 线程安全(thread-safe):即可以在多线程环境下安全使用。
    • 线程兼容(thread-compatible)且 不会被任何线程修改

示例 1: 数据竞争(Data Race)

代码示例:
vector<int> shared_vec = {0, 0};
void thread1() {// 修改第一个元素++shared_vec[0];
}
void thread2() {// 修改第二个元素++shared_vec[1];
}
分析:
  • 问题:多个线程对同一 shared_vec 进行操作,虽然它们操作的是不同的元素([0][1]),但如果没有合适的同步,可能会发生数据竞争。
  • 解决方案:对于不同的线程修改同一共享数据的情况,确保每个操作的同步性,或者确保每个线程访问的数据是独立的且不发生冲突。

示例 2: 线程不安全的 vector<bool>

代码示例:
vector<bool> shared_vec = {false, false};
void thread1() {// 修改第一个元素shared_vec[0] = true;
}
void thread2() {// 修改第二个元素shared_vec[1] = true;
}
分析:
  • 问题vector<bool> 是一个特别的案例,它并没有真正存储 bool 类型,而是以压缩的方式存储,导致其并不是线程安全的。在多个线程并发写入时会发生数据竞争。
  • 解决方案:避免使用 vector<bool>,或者确保线程间访问时的同步。

示例 3: 对 vector<int> 的并发修改

代码示例:
vector<int> shared_vec = {0, 0};
void thread1() {// 修改第一个元素++shared_vec[0];
}
void thread2() {// 修改第二个元素++shared_vec[1];
}
分析:
  • 问题:类似前面的例子,尽管 shared_vec[0]shared_vec[1] 是不同的元素,但它们仍然是 shared_vec 的成员,可能会发生数据竞争。
  • 解决方案:通过锁(如 std::mutex)来同步对 shared_vec 的访问,或者在访问时确保不会发生冲突。

示例 4: 并发访问 std::vector<int> 的部分范围

代码示例:
// 对迭代器区间内的每个元素进行加 1
template <typename Iterator>
void f(Iterator begin, Iterator end) {for (Iterator it = begin; it != end; ++it)++*it;
}
vector<int> v = {1, 2, 3};
void thread1() {f(v.begin(), v.begin() + 2);  // 修改前两个元素
}
void thread2() {f(v.begin() + 1, v.end());  // 修改后两个元素
}
分析:
  • 问题:这段代码在两个线程中并发执行,thread1thread2 都试图修改 v 中的不同部分([0, 1][1, 2])。这意味着两者在操作相同的元素时会发生数据竞争。
  • 解决方案:确保在访问共享资源时加锁,或者重构代码避免同时操作共享数据。

示例 5: 线程不安全的成员函数

代码示例:
class Widget {int* counter_;
public:Widget(int* counter) : counter_(counter) {}// 线程不安全!void Twiddle() {++*counter_;}
};
Widget MakeWidget();
void thread1() {Widget w = MakeWidget();w.Twiddle();
}
void thread2() {Widget w = MakeWidget();w.Twiddle();
}
分析:
  • 问题:多个线程可能同时调用 Twiddle(),而 counter_ 是一个指向共享数据的指针。如果没有同步机制,可能会导致数据竞争。
  • 解决方案:使用锁来保护 counter_ 的访问,确保每次只有一个线程可以修改共享的 counter_

推荐的最佳实践

对于库代码:
  1. 使类型线程兼容(thread-compatible):尽量设计为线程兼容,如果有必要,再设计为线程安全(thread-safe)。
  2. 明确文档化:清晰地文档化哪些类型是线程安全的,哪些是线程不兼容的。推荐将其他类型显式标注为线程兼容(thread-compatible)。
  3. 避免暴露内部状态:在设计中小心地暴露子对象,尤其是不可变的数据。
  4. 避免使用线程不安全的函数:如避免隐式共享的可变状态,避免使用指向共享数据的私有指针。
对于应用程序代码:
  1. 使共享对象线程安全:或者确保它们是线程兼容且不可变的。不要让它们成为并发操作的源头。

总结

  • 线程安全:在多线程环境中,不会因并发操作导致数据损坏或未定义行为。使用锁、原子操作等技术来确保线程安全。
  • 线程兼容:即对象可以在多个线程中并发访问,但不会被修改。如果没有修改操作,可以认为是线程兼容的。
  • API 竞争:多个线程对同一对象的不同操作可能会导致不一致的结果。通过合理设计数据访问和同步机制,可以避免这种情况。
    总之,确保多线程程序的正确性需要考虑对象的访问权限、线程安全性以及同步机制,尤其是避免并发访问导致的 API 竞争条件。

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

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

相关文章

热方程初边值问题解法

已知公式&#xff1a; u ( x , t ) ∫ − ∞ ∞ G ( x , y , t ) g ( y ) d y . u(x,t)\int_{-\infty}^{\infty}G(x,y,t)g(y)dy. u(x,t)∫−∞∞​G(x,y,t)g(y)dy. &#xff08;1&#xff09; 其中 G ( x , y , t ) 1 2 k π t e − ( x − y ) 2 4 k t G(x,y,t)\frac{1}{2…

怎样理解:source ~/.bash_profile

场景复现 $ source ~/.bash_profileAnalysis 分析 一句话概括 source ~/.bash_profile “在 当前 终端会话里&#xff0c;立刻执行并加载 ~/.bash_profile 中的所有命令&#xff0c;让其中定义的环境变量、函数、alias 等即时生效&#xff0c;而无需重新登录或开新 Shell。…

搜索问答技术概述:基于知识图谱与MRC的创新应用

目录 一、问答系统应用分析 二、搜索问答技术与系统 &#xff08;一&#xff09;需求和信息分析 问答需求类型 多样的数据源 文本组织形态 &#xff08;二&#xff09;主要问答技术介绍 发展和成熟度分析 重点问答技术基础&#xff1a;KBQA和DeepQA KBQA&#xff08;…

TCP数据的发送和接收

本篇文章结合实验对 TCP 数据传输中的重传机制、滑动窗口以及拥塞控制做简要的分析学习。 重传 实验环境 这里使用两台腾讯云服务器&#xff1a;vm-1&#xff08;172.19.0.3&#xff09;和vm-2&#xff08;172.19.0.6&#xff09;。 超时重传 首先 vm-1 作为服务端启动 nc…

python 保存二维数组到本地

Python中保存二维数组有多种方法&#xff0c;以下是常用的几种方式&#xff1a;1. 使用NumPy&#xff08;推荐&#xff09;import numpy as np# 创建二维数组 arr np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])# 保存为.npy文件&#xff08;NumPy专用格式&#xff09; np.save…

LIN总线通讯中从节点波特率同步原理

波特率同步原理&#xff1a;从节点如何通过0x55校准时钟&#xff1f; 一、同步场的核心作用&#xff1a;统一“时间标尺” 在LIN总线中&#xff0c;主节点与从节点各自拥有独立的时钟源&#xff08;如MCU内部RC振荡器&#xff09;&#xff0c;但由于制造工艺差异&#xff0c;…

【Unity笔记02】订阅事件-自动开门

流程 当玩家移动到触发区域的时候&#xff0c;门自动打开 事件系统 using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class EventSystem : MonoBehaviour {public static EventSystem Instance { get; private set; }…

控制台字符动画

旋转的立方体 #include <cstdint> #include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> #include <ctime> #include <thread> using namespace std;float angleX .0f; float a…

基于 PyTorch 的猫狗图像分类实战

基于 PyTorch 的猫狗图像分类实战 项目背景简介 深度学习框架 PyTorch 因其动态计算图和灵活易用性&#xff0c;被广泛应用于图像分类等计算机视觉任务。在入门计算机视觉领域时&#xff0c;常常以手写数字识别&#xff08;MNIST&#xff09;作为 “Hello World”&#xff0c…

SwiftUI 7(iOS 26 / iPadOS 26)中玻璃化标签页的全新玩法

&#x1f378; Liquid Glass 登场&#xff1a;界面设计焕然一新 WWDC25 可谓惊喜连连&#xff0c;其中最引人瞩目的变革之一&#xff0c;莫过于苹果推出的全新跨平台设计语言 —— Liquid Glass&#xff08;液态玻璃&#xff09;。这一设计风格涵盖了从按钮到导航栏&#xff0…

PDF处理控件Spire.PDF教程:在Java中读取PDF,提取文本、图片和表格

在数据驱动的现代开发中&#xff0c;高效处理 PDF 文档已成为 Java 开发者不可或缺的核心能力。无论是处理各类发票扫描件、业务分析报告&#xff0c;还是包含丰富图表的技术文档&#xff0c;掌握 Java 版的 PDF 解析技术都将大幅提升数据处理效率&#xff0c;充分释放文档中的…

跨平台游戏引擎 Axmol-2.7.0 发布

Axmol 2.7.0 版本是一个以错误修复和功能改进为主的次要LTS长期支持版本 &#x1f64f;感谢所有贡献者及财务赞助者&#xff1a;scorewarrior、peterkharitonov、duong、thienphuoc、bingsoo、asnagni、paulocoutinhox 重大变更 Android Studio 最低版本要求升级至 2025.1.1…

XML 笔记

<image src"hue.gif" width"100" height"auto" align"left"/> <br/> 换行 在 XML 中&#xff0c;<![CDATA[ 和 ]]> 用于定义一个 CDATA 节&#xff08;Character Data Section&#xff09;。CDATA 节是用于将一段…

Python实现优雅的目录结构打印工具

Python实现优雅的目录结构打印工具 在软件开发、系统管理和日常工作中&#xff0c;我们经常需要查看和分析目录结构。 工具功能概述 这个DirectoryPrinter类提供了以下功能&#xff1a; 递归打印目录结构可配置是否显示隐藏文件可设置最大递归深度自定义缩进和文件/文件夹符…

【Python】文件打开:with open具体解析

示例 # 使用 with 语句打开文件并读取内容 with open(pi.txt, r) as file_object:contents file_object.read()print(contents) # 文件在代码块结束后自动关闭with 解析 with 是 Python 中的上下文管理器语法&#xff0c;用于确保某个操作完成后自动执行清理操作。它常用于文…

Acrel-1000系列分布式光伏监控系统在湖北荆门一马光彩大市场屋顶光伏发电项目中应用

摘 要&#xff1a;分布式光伏发电能够对日益严重的环境压力起到有效缓解作用,在当前对环境保护需求越来越大情况下,发电行业在发展中不但要提升发电效率,同时也需要降低成本。分布式光伏发电主要是利用风能和太阳能等可再生清洁能源进行发电,对于空气质量具有改善效果,和传统发…

CentOS-6与CentOS-7的网络配置IP设置方式对比 笔记250706

CentOS-6与CentOS-7的网络配置IP设置方式对比 笔记250706 1️⃣ 参考 1 CentOS-6 与 CentOS-7 的网络配置IP设置方式对比 CentOS 6 和 CentOS 7 在网络配置上存在显著差异&#xff0c;主要体现在配置文件结构、管理工具、服务机制和命令集等方面。以下是两者的核心对比&#x…

【网络系列】HTTP 429 状态码

博客目录 HTTP 429 状态码的定义与背景产生 429 错误的常见场景1. API 速率限制触发2. 网络爬虫行为被检测3. 分布式拒绝服务(DDoS)防护4. 用户/IP 特定限流策略5. 应用程序逻辑错误 深入解读 429 响应的关键头部信息Retry-After 头部X-RateLimit 系列头部RateLimit 标准化头部…

C++无锁数据结构:CAS(Compare-and-Swap)

在高并发场景下&#xff0c;传统锁机制带来的线程阻塞和上下文切换开销成为性能瓶颈。无锁数据结构通过原子操作实现线程安全&#xff0c;避免了锁的使用&#xff0c;成为高性能系统的关键技术。本文将深入探讨C中基于CAS&#xff08;Compare-and-Swap&#xff09;的无锁数据结…

【数字图像处理】

数字图像处理 绪论1. 数字图像处理基本概念2. 数字图像处理系统的组成3. 数字图像处理技术研究的内容4. 数字图像处理技术的应用领域5. 图像处理技术涉及的学科领域 图像处理基础1. 电磁波谱与可见光谱2. 人眼的亮度视觉特性3. 图像的表示4. 空间分辨率和灰度级分辨率5. 像素间…