当前位置: 首页 > news >正文

网站后台任务旅游网站代码html

网站后台任务,旅游网站代码html,抖音代运营策划方案,wordpress添加数据库文件#x1f493;博主CSDN主页:麻辣韭菜#x1f493;   ⏩专栏分类#xff1a;C修炼之路⏪   #x1f69a;代码仓库:C高阶#x1f69a;   #x1f339;关注我#x1faf5;带你学习更多C知识   #x1f51d;#x1f51d; 目录 前言 一、thread类的简单介绍 get_id… 博主CSDN主页:麻辣韭菜   ⏩专栏分类C修炼之路⏪   代码仓库:C高阶   关注我带你学习更多C知识   目录 前言 一、thread类的简单介绍   get_id ​编辑 构造函数  ​编辑 joinable join​编辑 native_handle  detach  std::thread::operator​编辑 std::thread::swap​编辑 二、mutex  mutex recursive_mutex 三、RAII 风格的锁  四、条件变量 condition_variable 等待函数 通知函数 交替打印数字  五、原子类 atomic load、store ​编辑 六、包装器 bind 前言 在C11之前由于没有线程库这就导致了在Linux能跑的代码在windows下就不行反之也是一样。导致了代码的可移植性差而C11之后出现了线程库并行编译时不需要依赖第三方库。而且在原子操作中还引入了原子类的概念。 一、thread类的简单介绍   类定义 std::thread是C标准库中的一个类用于表示和控制线程。它允许你创建、管理、同步线程并且可以与操作系统的线程进行交互。 成员类型 id: 线程的唯一标识符。它是一个类型为std::thread::id的公共成员类型可以用来比较不同线程是否相同。native_handle_type: 表示线程的原生操作系统句柄。这个类型是平台相关的可以用来与操作系统的API进行交互。 成员函数 构造函数: 可以以多种方式构造std::thread对象比如传递一个可调用对象函数、lambda表达式、函数对象等作为参数。析构函数: 当std::thread对象被销毁时如果它是一个可加入的线程并且没有被join或detach那么析构函数会调用std::terminate这将终止程序。移动赋值操作符 operator: 允许线程对象之间的所有权转移。移动之后源线程对象将不再代表一个活跃的线程。get_id: 返回当前线程的唯一标识符。joinable: 检查线程是否可加入。如果线程已经加入或者分离返回false。join: 等待线程结束。如果线程已经结束了调用join将不会做任何事情。detach: 分离线程使其在完成时不会自动销毁。分离后的线程必须由操作系统来管理。swap: 交换两个线程对象的内部状态使得一个线程对象代表另一个线程的执行。native_handle: 返回线程的原生句柄这可以用来直接与操作系统的线程管理功能交互。hardware_concurrency: 静态成员函数返回硬件支持的线程并发数量即可以同时运行的线程数。 非成员函数 swap: 一个非成员函数用于交换两个std::thread对象的线程。它提供了与成员swap函数相同的功能但是可以用于按值传递线程对象。 我们先来一个传统的写法 #include iostream #include thread using namespace std; void Func(int n, int num) {for (int i 0; i num; i){cout 线程 n i endl;}cout endl;} int main() {thread t1(Func, 1, 20);thread t2(Func, 2, 30);t1.join();t2.join();return 0; } 线程的ID如何获取 get_id 在C标准库中std::thread::get_id是一个公共成员函数属于std::thread类。这个函数用于获取与std::thread对象关联的线程的唯一标识符thread id。以下是关于std::thread::get_id成员函数的详细信息 函数原型 id get_id() const noexcept; 功能描述 std::thread::get_id函数返回一个std::thread::id类型的值该值是线程的唯一标识符。 行为 如果std::thread对象是可加入的joinable即它代表了一个活跃的线程那么get_id函数将返回一个值该值唯一地标识了这个线程。如果std::thread对象不是可加入的例如它是默认构造的或者已经被移动move了那么get_id函数将返回一个默认构造的对象该对象不代表任何活跃的线程。 参数 无该函数不接受任何参数。 返回值 当线程对象是可加入的返回一个std::thread::id类型的值它唯一标识了线程。当线程对象不是可加入的返回一个默认构造的std::thread::id对象。 异常安全性 get_id函数被声明为noexcept这意味着它保证不会抛出异常。 代码示例 #include iostream #include thread using namespace std; #include vector int main() {int m, n;cin m n;vectorint arr;arr.push_back(m);arr.push_back(n);vectorthread vthds(m);for (int i 0; i arr[0]; i){vthds[i] thread([i,arr,vthds](){for (int j 0; j arr[1]; j){cout 线程: vthds[i].get_id() j: j endl;}cout endl;});}for (auto t : vthds){t.join();}return 0; } 但是我们一般都不会用对象去调用而是用this_thread std::this_thread 是 C 标准库 thread 中定义的一个命名空间它包含了一组与当前线程相关的函数。这些函数提供了对当前线程的访问和控制允许开发者执行如休眠当前线程、获取当前线程的ID等操作。以下是 std::this_thread 命名空间中的一些常用功能 成员函数 sleep_for: 使当前线程暂停执行指定的时间长度。例如std::this_thread::sleep_for(std::chrono::seconds(1)); 会使当前线程休眠一秒。sleep_until: 使当前线程休眠直到达到某个指定的时间点。yield: 暗示调度器当前线程愿意让出对处理器的使用调度器可以选择另一个线程来运行。get_id: 返回一个标识当前线程的 std::thread::id 类型的唯一标识符。 原子操作 CAS 是一个不断重复尝试的过程如果尝试的时间过久就会影响整体效率因为此时是在做无用功而 yield 可以主动让出当前线程的时间片避免大量重复把 CPU 资源让出去从而提高整体效率 具体实现原理可以看陈浩大佬无锁队列的实现 | 酷 壳 - CoolShell 构造函数  默认构造函数 当你使用 std::thread 的默认构造函数时你得到的是一个没有关联任何线程的线程对象。这相当于一个空壳没有实际的执行内容。这个构造函数通常用于那些可能但不一定需要与线程关联的情况。 初始化构造函数 这种构造函数允许你创建一个新的线程对象并立即启动一个线程执行指定的函数或任务。你可以传递函数和相应的参数给构造函数这些参数将被复制或移动到新线程中根据它们的值类别即左值或右值。重要的是新线程的执行开始于构造函数完成时这意味着一旦线程对象被创建它就开始执行指定的任务。 复制构造函数已删除 C中的 std::thread 对象不支持复制语义。这意味着你不能通过简单的复制操作来创建线程对象的副本。这是因为复制线程对象可能导致多个对象尝试管理同一个底层线程这会带来同步和生命周期管理上的复杂性。 移动构造函数 移动构造函数允许你将一个线程对象的所有权转移给另一个线程对象。这通常发生在你需要重新分配线程资源时。例如如果你在一个线程对象中创建了一个线程但后来决定将其转移给另一个线程对象来管理你可以使用移动构造函数来实现这一点。转移后原始线程对象将不再关联任何线程而新的对象将接管线程的执行。 销毁 当一个 std::thread 对象的生命周期结束时如果它是一个可加入的线程即它通过初始化构造函数创建并启动了一个线程它必须被适当地处理。你可以通过调用 join() 方法来等待线程完成其任务或者通过调用 detach() 方法来分离线程使其在没有管理的情况下继续执行。如果可加入的线程对象在销毁前既没有被加入也没有被分离程序将调用 std::terminate这将导致程序立即终止。 joinable std::thread::joinable 是 C 标准库 thread 中 std::thread 类的一个成员函数它用于检查线程对象是否可加入。以下是对这个函数的详细解释 函数原型 bool joinable() const noexcept; 功能描述 joinable() 函数返回一个布尔值指示线程对象是否可加入。如果线程对象关联了一个线程执行流并且该线程尚未结束那么它就是可加入的。 行为 可加入的线程对象如果线程对象在创建时通过初始化构造函数与一个新线程关联或者它通过移动构造函数从另一个线程对象那里接管了线程执行那么它是可加入的。不可加入的线程对象如果线程对象是以下情况之一它将不可加入 使用默认构造函数创建的没有关联任何线程执行。通过移动操作从其他线程对象转移而来原对象不再关联任何线程。对象的 join() 或 detach() 成员函数已经被调用过线程已经完成或分离。 注意事项 joinable() 函数被声明为 noexcept意味着它保证不会抛出异常。在调用 join() 或 detach() 之前使用 joinable() 进行检查是一种良好的编程实践可以避免对已经结束或分离的线程执行非法操作。 #include iostream #include threadvoid thread_function() {// 线程执行的代码 }int main() {std::thread t(thread_function);// 等待线程结束if (t.joinable()) {t.join(); // 等待线程完成} else {std::cout Thread is not joinable. std::endl;}return 0; } 在这个示例中我们首先创建了一个线程 t。然后我们检查它是否可加入。如果是我们调用 join() 等待线程结束。如果线程不可加入我们将输出一条消息说明这一点。 总结 std::thread::joinable 是一个重要的成员函数它提供了一种机制来判断线程对象是否可以安全地调用 join() 或 detach() 函数。正确使用 joinable() 可以帮助避免多线程编程中的常见错误和潜在的资源管理问题。 join std::thread::join 是 C 标准库中 thread 头文件定义的 std::thread 类的一个成员函数用于等待由 std::thread 对象表示的线程完成其执行。以下是对该函数的详细说明 函数原型 void join(); 功能描述 join() 函数的作用是等待当前 std::thread 对象所关联的线程执行完成。调用此函数的线程通常是主线程或其他线程将被阻塞直到 std::thread 对象所代表的线程终止。 行为 当 join() 被调用时如果 std::thread 对象所关联的线程尚未结束调用线程将等待直到该线程完成其所有操作。join() 函数与线程完成的所有操作同步这意味着一旦 join() 返回被等待线程中的所有工作都已经完成。 效果 在 join() 函数调用后std::thread 对象将变为不可加入状态。这意味着你不能再次对同一个线程对象调用 join() 或 detach()。一旦线程对象变为不可加入状态它就可以被安全地销毁因为它不再关联任何活跃的线程。 注意事项 如果 std::thread 对象是默认构造的或者已经被移动到另一个 std::thread 对象或者已经调用过 join() 或 detach()则 join() 函数将立即返回不会产生阻塞效果。在多线程程序中合理使用 join() 可以确保线程的执行结果被正确处理并且线程资源得到适当释放。 native_handle  std::thread::native_handle() 是 C 标准库 thread 中 std::thread 类的一个成员函数它用于获取与线程对象关联的原生线程句柄。以下是对这个函数的详细解释 函数原型 native_handle_type native_handle(); 功能描述 native_handle() 函数返回一个特定于实现的值这个值提供了对底层操作系统线程表示的访问。这个原生句柄可以用于直接与操作系统的线程管理功能交互例如查询线程状态或执行特定于平台的线程操作。 行为 此函数只有在库的实现支持时才存在于 std::thread 类中。它返回一个 thread::native_handle_type 类型的值这个值是特定于实现的并且可以用来操作底层线程。 参数 此函数不接受任何参数。 返回值 返回一个 thread::native_handle_type 类型的值表示线程的原生句柄。 注意事项 native_handle() 函数的使用可能会涉及未指定的数据竞争和异常安全性问题。使用此函数时需要确保对线程的访问是同步的并且考虑到可能的异常安全问题。由于 native_handle_type 是特定于实现的它的具体类型和使用方法将依赖于编译器和操作系统。因此使用 native_handle() 可能需要特定平台的编程知识。 这个了解就好了。  detach  std::thread::detach 是 C 标准库 thread 头文件中 std::thread 类的一个成员函数用于分离线程对象。以下是对这个函数的详细解释 函数原型 void detach(); 功能描述 detach() 函数将 std::thread 对象所代表的线程与调用它的线程分离允许这两个线程独立执行。分离操作意味着两个线程将继续它们的执行而不会相互阻塞或同步。 行为 分离线程后原线程对象不再控制或等待被分离的线程结束。如果分离的线程结束执行它的资源将被操作系统自动释放。如果原线程调用 detach() 的线程在分离的线程结束之前结束分离的线程将继续运行直到它完成执行然后由操作系统释放它的资源。 效果 调用 detach() 后std::thread 对象变为不可加入状态即你不能对这个对象调用 join()。不可加入的线程对象可以被安全地销毁因为它不再持有对任何活跃线程的引用。 注意事项 分离线程是一个不可逆的操作。一旦调用了 detach()你将无法再等待或加入这个线程。如果分离的线程在执行过程中发生异常调用 detach() 的线程将不会得到通知异常也不会传播到调用线程。在分离线程之前确保线程的执行不会导致资源泄漏或未完成的任务。 #include iostream #include threadvoid thread_function() {std::cout 线程正在执行工作... std::endl;// 模拟一些工作负载std::this_thread::sleep_for(std::chrono::seconds(2));std::cout 线程工作完成。 std::endl; }int main() {std::thread t(thread_function);// 分离线程t.detach();// 主线程继续执行不会等待分离的线程结束std::cout 主线程继续执行不会等待线程 t.get_id() 结束。 std::endl;// 由于线程已经被分离这里调用 join() 将无效果// t.join(); // 这行代码将导致未定义行为return 0; } 总结         std::thread::detach 是一个重要的成员函数它提供了一种机制来分离线程使得线程可以独立于创建它的线程执行。使用 detach() 可以避免不必要的同步等待但也需要谨慎使用以确保资源得到正确管理。 std::thread::operator 功能描述 移动赋值操作符允许一个std::thread对象通常称为右侧对象rhs将其线程执行的所有权转移给另一个std::thread对象通常称为左侧对象*this。这个操作是C移动语义的一部分用于高效地重新分配资源。 行为 如果左侧对象*this当前不是可加入的即它不关联任何线程或者已经分离了线程它将接管右侧对象rhs所代表的线程执行。这包括关联的线程和其所有状态。 如果左侧对象是可加入的即它关联了一个活跃的线程则调用terminate()函数。这将尝试立即终止线程的执行这可能导致资源未被正确释放或数据不一致。移动赋值操作之后右侧对象rhs将不再代表任何线程执行。它就像使用默认构造函数创建的对象一样不关联任何线程。 注意事项 移动赋值操作符是不可逆的。一旦执行原对象的状态将被清除并且不能再用来控制或同步线程。std::thread对象不能被复制只能被移动。这意味着没有复制构造函数只有移动构造函数和移动赋值操作符。在移动赋值之后原对象应该被视为无效并且不应该再被使用来控制线程。 #include iostream #include threadvoid thread_function() {std::cout 线程正在执行工作... std::endl;// 模拟一些工作负载std::this_thread::sleep_for(std::chrono::seconds(1));std::cout 线程工作完成。 std::endl; }int main() {std::thread t1(thread_function); // 创建并启动线程std::thread t2; // 创建一个空的线程对象// 移动t1中的线程所有权到t2t2 std::move(t1); // t1 现在不再代表任何线程// 等待t2中的线程完成if (t2.joinable()) {t2.join();}// 尝试使用已经移动的t1将导致未定义行为// if (t1.joinable()) {// t1.join(); // 这将是错误的使用// }return 0; } std::thread::swap 函数原型 void swap(std::thread x); 功能描述 swap 函数交换调用对象*this和参数 x 的线程状态。这意味着两个 std::thread 对象关联的线程执行将被交换。 行为 如果调用对象和 x 都是可加入的它们关联的线程将被交换。如果其中一个或两个是不可加入的例如它们已经被分离或默认构造的那么交换操作将只影响可加入的线程对象。 参数 x要与当前对象交换状态的 std::thread 对象。 返回值 无void 类型。 thread类的成员函数介绍完了 最先开始的代码打印错乱。 显示器也是临界资源两个线程共享临界资源同时往显示器上打印就会出现错乱的问题。这时候我们就需要用到锁了。 二、mutex  互斥体类型Mutex Types 这些是用于保护代码的关键部分以实现互斥访问的可锁类型 mutex基本的互斥体提供独占访问。recursive_mutex递归互斥体允许同一个线程多次锁定它。timed_mutex带超时功能的互斥体尝试锁定时可以指定超时时间。recursive_timed_mutex递归带超时功能的互斥体结合了上述两种特性。 锁类型Locks 这些对象管理互斥体的锁定状态并将其与对象的生命周期相关联 lock_guard当构造时锁定互斥体在析构时自动解锁。它提供了作用域锁定。unique_lock与lock_guard类似但提供了更多的灵活性例如尝试锁定和锁定超时。 函数 这些函数用于更高级的锁定操作 std::lock原子地锁定多个互斥体防止死锁。std::try_lock尝试锁定一个或多个互斥体如果无法立即锁定则可以不阻塞地失败。std::call_once确保某个函数通常用于初始化在程序的生命周期内只被调用一次即使多次请求也是如此。 上面的锁我们一步一步的来先说mutex的lock。 mutex #include vector #include mutex int main() {int m, n;cin m n;vectorint arr;mutex mtx;arr.push_back(m);arr.push_back(n);vectorthread vthds(m);for (int i 0; i arr[0]; i){vthds[i] thread([i,arr,mtx](){mtx.lock();for (int j 0; j arr[1]; j){cout 线程: this_thread::get_id() j: j endl;this_thread::sleep_for(chrono::milliseconds(200));}mtx.unlock();cout endl;});}for (auto t : vthds){t.join();}return 0; } 这里从运行结果来看我们打印的结果没有错乱了。但是这些线程是并行在跑并不是我们想要的。  我们再来看一段代码 void Func(int n) {// 并行for (int i 0; i n; i){ x;} } int main() {int n 10000;thread t1(Func, n);thread t2(Func, n);t1.join();t2.join();cout x endl;return 0; }当我们的n较小时x为正确如果x的值比较大又会是什么样 比如n为100000 结果超出我们预期。这里的x是全局变量和显示器一样都是临界资源。两个线程同时对它就会出现线程安全的问题这时我们也是需要加锁。  这里就有个问题了如何加锁      理论来说并行是要比串行快的这里串行快是因为代码比较简单while 循环内只需要进行 x 就行了并行化中频繁加锁、解锁的开销要远大于串行化单纯的进行 while 循环 如果多来点IO操作就会发现并行快 。 #includemutexint main() {int n 100000;int x 0;mutex mtx;size_t begin clock();thread t1([,n](){mtx.lock();for (int i 0; i n; i){cout x endl;cout n endl;x;}mtx.unlock();});thread t2([,n]() {for (int i 0; i n; i){cout x endl;cout n endl;mtx.lock();x;mtx.unlock();}});t1.join();t2.join();size_t end clock();cout x endl;cout end - begin endl;return 0; } 这时我们就加了两条打印语句两者差距就很接近了。如果还有其他不涉及临界资源的语句明显并行就快了。因为没有锁的情况其他线程是可以执行其他语句的。但串行只能拿到锁后才能执行。  这个大家根据实际的场景来选择加锁的位置。  recursive_mutex #includemutexint x 0; recursive_mutex mtx;void Func(int n) {if (n 0)return;mtx.lock();x;Func(n - 1);mtx.unlock(); }int main() {thread t1(Func, 10000);thread t2(Func, 20000);t1.join();t2.join();cout x endl;return 0; }上面这种代码就是典型的死锁问题即使我们把解锁放在x后面也会出现栈溢出的问题。所以能写循环的尽量在循环体里面用互斥锁递归互斥锁尽量不要用。 后面两种不常用这里就不做过多讲解。 三、RAII 风格的锁  上面都是需要我们自己手动加锁和解锁万一我们有时候忘记加锁和解锁即使我们没有忘记手动加锁和解锁代码很容易出现死锁的问题。比如后面章节要讲解的异常代码引入异常处理之后如果是临界资源出现了问题代码会跳转至 catch 中 捕捉异常。这时代码异常退出。而unlock还没有执行。锁的资源没有释放这就导致死锁。 int main() {int n 100000;int x 0;mutex mtx;thread t1([, n]() {try {mtx.lock();for (int i 0; i n; i){if (i % 2 0)throw exception(异常);x;}mtx.unlock();}catch (const exception e){cout e.what() endl;}});thread t2([, n]() {for (int i 0; i n; i){mtx.lock();x;mtx.unlock();}});t1.join();t2.join();size_t end clock();cout x endl;return 0; } 上面的代码出现的死锁的问题就导致t2这线程一直在申请锁资源但是一直申请不到锁。进程卡卡住。 修改之前的代码不用自己手动加锁和解锁 文档是关于对lock_guard介绍。 说人话这里lock_guard利用类的特性对象实例化时会自动调用构造函数出对象作用域时会自动调用析构函数。而构造函数就是加锁析构函数就是解锁。 还有一种   lock_guard和unique_lock都是C11标准库中的互斥锁管理工具用于简化互斥锁的使用和管理它们都遵循RAII资源获取即初始化原则确保在作用域结束时自动释放锁。尽管它们的基本功能相似但它们之间存在一些关键的区别 自动类型转换 lock_guard不提供对锁类型的自动转换。它需要在构造时显式指定互斥锁的类型。unique_lock提供了对mutex和recursive_mutex的自动类型转换允许使用相同的模板代码来锁定不同类型的互斥锁。 递归锁支持 lock_guard不支持递归互斥锁recursive_mutex因为它的设计不包括递归锁定的能力。unique_lock可以与递归互斥锁一起使用允许同一个线程多次锁定同一个递归互斥锁。 锁所有权转移 lock_guard不支持转移锁的所有权。一旦构造它就会锁定互斥锁并在销毁时自动解锁。unique_lock允许通过移动语义转移锁的所有权。例如可以将一个unique_lock对象的锁所有权移动到另一个unique_lock对象。 锁的尝试与释放 lock_guard不支持尝试锁定或手动释放锁。它在构造时锁定互斥锁并在析构时解锁。unique_lock提供了try_lock、try_lock_for、try_lock_until等成员函数来尝试锁定互斥锁以及release成员函数来手动释放锁如果已经锁定的话。 使用场景 lock_guard适用于简单的锁管理当你知道在作用域结束时需要释放锁并且不需要尝试锁定或转移锁所有权时。unique_lock适用于更复杂的场景可能需要尝试锁定、定时锁定、递归锁定或转移锁所有权。 性能 由于unique_lock提供了更多的功能它可能比lock_guard有更多的运行时开销。然而如果你需要这些额外的功能unique_lock是更合适的选择。 总结来说lock_guard是一个更简单的锁管理工具适用于不需要额外功能的简单场景。而unique_lock提供了更多的灵活性和控制能力适用于需要这些高级特性的复杂场景。 四、条件变量 condition_variable 我们加入锁之后线程之间互斥了为了让他们同步需要用到条件变量。 condition_variable 是 C 标准库中的一个头文件它声明了与条件变量相关的类型和函数。条件变量是一种同步机制用于在多线程编程中让一个或多个线程等待某个条件为真直到被另一个线程通知。  等待函数 waitvoid wait(std::unique_lockstd::mutex lock);等待另一个线程的通知。调用此函数的线程必须已经通过 unique_lock 对一个互斥锁进行了加锁。当调用 wait 时线程将释放互斥锁并进入等待状态直到被另一个线程通知。wait_fortemplate class Rep, class Period cv_status wait_for(std::unique_lockstd::mutex lock, const std::chrono::durationRep, Period timeout_duration);与 wait 类似但增加了超时时间。如果在超时时间内没有收到通知线程将退出等待状态。wait_untiltemplate class Clock, class Duration cv_status wait_until(std::unique_lockstd::mutex lock, const std::chrono::time_pointClock, Duration timeout_time);与 wait_for 类似但超时条件是指定的时间点而不是持续时间。 通知函数 notify_onevoid notify_one();唤醒在该条件变量上等待的一个线程。如果有多个线程在等待将唤醒其中一个线程。如果没有任何线程在等待则不执行任何操作。notify_allvoid notify_all();唤醒所有在该条件变量上等待的线程。 std::condition_variable 对象在使用时通常与 std::unique_lock 或 std::mutex 结合使用以确保在等待和通知过程中对共享资源的访问是互斥的。使用条件变量可以避免竞态条件并实现更高效的线程间通信 交替打印数字  题目要求给你两个线程 t1、t2要求 t1 打印奇数t2 打印偶数数字范围为 [1, 100]两个线程必须交替打印 这里有两个问题 1 如何确定t1先打印 这个我们可以利用条件变量的wait函数的特性如果一个线程等待会自动释放锁。当x1时是奇数这时t1就一定会先运行。 2.如何让另一个线程不打印 利用条件条件变量 分别让t1、t2满足条件进行阻塞等待。 #include thread #include condition_variable #include mutex int x 1; int n 100; mutex mtx; condition_variable cv; void fucn1(int n) {while(true){unique_lockmutex lck(mtx);if (x 100)break;if (x % 2 0) //是偶数就阻塞等待cv.wait(lck);cout t1: this_thread::get_id() : x endl;x;cv.notify_one();} } void fucn2(int n) {while(true){unique_lockmutex lck(mtx);if (x 100)break;if (x % 2 ! 0) //是奇数就阻塞等待cv.wait(lck);cout t2: this_thread::get_id() : x endl;x;cv.notify_one();}} int main() {thread t1(fucn1, n);thread t2(fucn2, n);t1.join();t2.join();return 0; } 五、原子类 atomic 前面提到频繁的加锁、解锁会导致效率下降比如上面的代码其实临界资源就只有一个x我们对它或者-- 再或者位操作。有没有其他办法不用加锁有的原子操作 atomic 是 C 标准库中的一个头文件它提供了一系列用于实现原子操作的模板类和类型定义。原子操作是保证在多线程环境中安全执行的指令不会出现数据竞争的问题。 类 atomic原子模板类封装了一个可以原子地访问的值。这个模板支持多种数据类型如 int、float、pointer 等以及它们的无符号和长整型版本。atomic_flag原子标志类用于实现低级别的同步操作如自旋锁。 通用原子操作 is_lock_free检查原子操作是否无锁即是否使用非阻塞算法。store将一个值存储到原子对象中可以指定内存顺序。load从原子对象中读取值可以指定内存顺序。operator T获取原子对象中的值的副本。exchange交换原子对象中的值并返回旧值。compare_exchange_weak弱比较并交换操作用于实现原子条件赋值。compare_exchange_strong强比较并交换操作同样用于原子条件赋值。 特定专业化支持的操作例如整数和/或指针 fetch_add将一个值加到原子对象上并返回原始值。fetch_sub从原子对象中减去一个值并返回原始值。fetch_and对原子对象中的值应用按位与操作并返回原始值。fetch_or对原子对象中的值应用按位或操作并返回原始值。fetch_xor对原子对象中的值应用按位异或操作并返回原始值。operator 和 operator--递增和递减原子对象中的值。复合赋值运算符如 、-、、|、^ 等提供复合赋值操作。 #include iostream #include atomic #include thread #include vectorstd::atomicint counter(0); // 线程安全的原子计数器int main() {const int num_threads 4;std::vectorstd::thread threads;// 使用 lambda 表达式创建并启动线程for (int i 0; i num_threads; i) {threads.push_back(std::thread([]() {int n 100; // 每个线程递增的次数for (int j 0; j n; j) {// 使用 fetch_add 原子地递增 countercounter.fetch_add(1, std::memory_order_relaxed);}}));}// 等待所有线程完成for (auto th : threads) {if (th.joinable()) {th.join();}}// 输出最终的计数器值std::cout Final counter value: counter std::endl;return 0; } 这里使用 std::memory_order_relaxed 因为我们不关心操作的内存顺序只要求操作是原子的。 如果是用printf打印就会编译出错 load、store  因为counter是原子类型、而我们的是%d类型不匹配。这时我们可以用load  #include atomic #include iostreamint main() {std::atomicint atomicInt(0);// 使用 store 写入原子变量atomicInt.store(10, std::memory_order_relaxed); // 写入值10// 使用 load 读取原子变量int value atomicInt.load(std::memory_order_acquire); // 读取原子变量的值std::cout Value of atomicInt: value std::endl;return 0; } 使用 load 的场景 读取共享数据当需要读取由多个线程共享的原子变量的值时使用 load 可以确保读取操作的原子性和内存顺序防止读取过程中其他线程的写入干扰。内存顺序要求当对内存顺序有特定要求例如需要保证某个操作的内存效果对其他线程可见时可以使用 load 并指定适当的内存顺序参数如 std::memory_order_acquire。 使用 store 的场景 写入共享数据当需要修改由多个线程共享的原子变量的值时使用 store 可以确保写入操作的原子性和内存顺序防止写入过程中其他线程的读取干扰。发布操作在发布-订阅模式中当一个线程创建了一个对象并希望其他线程能够安全地访问这个对象时可以使用 store 并指定 std::memory_order_release 来确保对象的构造和发布操作对其他线程可见。 六、包装器 在C中什么可以被调用函数对象仿函数、函数指针、函数名、lambda、这些都是可以被调用的那这么多类型我们用模板传参时可能会导致效率低下。 所以C11推出了包装器也叫做适配器C中的function本质是一个类模板也是一个包装器。 底层用的还是仿函数。 int f(int a, int b) {cout int f(int a, int b) endl;return a b; }struct Functor { public:int operator() (int a, int b){cout int operator() (int a, int b) endl;return a b;} }; 就比如上面我要用map进行封装可是他们的类型不同模板参数如何传参 mapstring, 这时候我们需要用包装器 我们来一个简单的用法 int main() {//int(*pf1)(int,int) f;//mapstring, functionint(int, int) f1 f;functionint(int, int) f2 Functor();functionint(int, int) f3 [](int a, int b) {cout [](int a, int b) {return a b;} endl;return a b;};cout f1(1, 2) endl;cout f2(10, 20) endl;cout f3(100, 200) endl;return 0; } 上面代码f1 f2 f3就用functional这个类进行包装了类型一样那map就可以进行模板参数传参了。下面在网络用的比较多指令集。什么指令执行什么任务。 int main() {//int(*pf1)(int,int) f;//mapstring, mapstring, functionint(int, int) opFuncMap;opFuncMap[函数指针] f;opFuncMap[仿函数] Functor();opFuncMap[lambda] [](int a, int b) {cout [](int a, int b) {return a b;} endl;return a b;};cout opFuncMap[函数指针](1, 2) endl;cout opFuncMap[仿函数](1, 2) endl;cout opFuncMap[lambda](1, 2) endl;return 0; } 包装器对于类成员函数也有不同比如普通成员函数和静态成员函数的包装就不一样 class Plus { public:Plus(int rate 2):_rate(rate){}static int plusi(int a, int b){return a b;}double plusd(double a, double b){return (a b)* _rate;}private:int _rate 2; }; int main() {//functionint(int, int) f1 Plus::plusi;functionint(int, int) f1 Plus::plusi; //静态成员函数正常包装functiondouble(Plus, double, double) f2 Plus::plusd; //普通成员函数//模板参数要有this*指针 但是实际上不用直接传对象而赋值时要加不加编译不过cout f1(1, 2) endl;cout f2(Plus(), 20, 20) endl;Plus pl(3);cout f2(pl, 20, 20) endl;return 0; } bind bind有两个作用:一是交换参数 二是修个参数个数 基本用法 std::bind 的基本语法如下 std::bindReturnType(Function, Args...)(std::placeholders::_1, ...); 其中 ReturnType 是被绑定函数的返回类型。Function 是要绑定的可调用对象。Args... 是要绑定的参数列表。std::placeholders::_1, ... 是用于占位的参数表示将来调用时需要提供的参数。 #include iostream #include functionalvoid print(int a, int b) {std::cout a and b std::endl; }int main() {print(10, 5);// 创建一个 std::function 对象绑定 print_sum 函数// 但是交换了参数的顺序auto swapped_function std::bind(print, std::placeholders::_1, std::placeholders::_2);// 调用交换参数后的函数swapped_function(5, 10); // 输出 The sum of 10 and 5 is 15return 0; } 关于参数交换这个用处不大。 修改参数个数场景还是很多的。比如一个类的成员函数是加减乘除实际这个类的成员函数的参数是多少 成员函数参数的个数是n1个类还有隐藏的this*啊。 如果用包装器进行包装比如现在把类和lambda还有仿函数一起包装起来。问题是lambda只有n个参数这时就需要用bind来修改参数个数。 #include iostream #include functional #include map #include string// 一个示例类 class Calculator { public:// 类成员函数接受两个参数int add(int a, int b) {return a b;} };// 一个自由函数接受两个参数 int multiply(int a, int b) {return a * b; }int main() {//用bind直接绑死对象。std::functionint(int, int) add1 std::bind(Calculator::add, Calculator(), std::placeholders::_1, std::placeholders::_2);std::mapstd::string, std::functionint(int, int) funcMap {{*,multiply},{,add1 }};//std::cout funcMap[*](1, 2) std::endl;//std::cout funcMap[](10, 30) std::endl;for (auto e : funcMap){std::cout [ e.first ]: e.second(10, 20) std::endl;}return 0; } C11 的常用内容到这里就讲解完毕了下节预告异常智能指针关注我带你学习更多C知识。
http://www.w-s-a.com/news/631921/

相关文章:

  • 景区旅游网站平台建设公司企业网站源码
  • 免费高清网站推荐喂来苏州网络科技有限公司
  • php做的大型网站有哪些备案博客域名做视频网站会怎么样
  • 去哪网站备案吗昭通网站建设
  • flash企业网站源码建筑材料采购网站
  • 网站可以换虚拟主机吗部门做网站优点
  • 如何做分类网站信息营销莱芜网页定制
  • 班级网站建设感想中国做视频网站有哪些
  • 做刷票的网站wordpress图片链接插件
  • 给客户做网站图片侵权沈阳做网站的地方
  • 网站开发步骤规划蓝天云免费空间主机
  • 网站字体规范wordpress找不到页面内容编辑
  • 静态网站建设参考文献茂名营销型网站制作公司
  • 君山区建设局网站风铃微网站怎么做
  • 购物网站销售管理合肥网络推广平台
  • 网站建设规划书txt微盘注册帐号
  • 小说网站开发实训报告企业网盘收费标准
  • mvc网站开发医疗医院网站建设
  • 天津市建设厅官方网站wordpress设置404
  • 贵阳好的网站建设免费正能量网站下载ww
  • 免费学习的网站平台自建站seo如何做
  • 海南三亚做网站公众号版面设计创意
  • 学校网站建设目的与意义合肥网页定制
  • 网站查询地址网站建设与维护费用
  • 做网站哪些软件比较好合肥外贸网站建设公司
  • 建网站需要哪些条件专业网站设计报价
  • 定制网站开发技术化妆品的网站布局设计图片大全
  • 网站模糊设计发布产品的免费平台有哪些
  • 网站建站什么目录桂林网站建设内容
  • 光明新区城市建设局网站长沙营销型网站制作费用