私人让做彩票网站吗,8免费建站网站,深圳app开发公司,WordPress右下角提醒hello #xff01;大家好呀#xff01; 欢迎大家来到我的Linux高性能服务器编程系列之《线程纵横#xff1a;C并发编程的深度解析与实践》#xff0c;在这篇文章中#xff0c;你将会学习到C新特性#xff0c;并发编程#xff0c;以及其如何带来的高性能的魅力#xff0… hello 大家好呀 欢迎大家来到我的Linux高性能服务器编程系列之《线程纵横C并发编程的深度解析与实践》在这篇文章中你将会学习到C新特性并发编程以及其如何带来的高性能的魅力以及手绘UML图来帮助大家来理解希望能让大家更能了解网络编程技术 希望这篇文章能对你有所帮助大家要是觉得我写的不错的话那就点点免费的小爱心吧注这章对于高性能服务器的架构非常重要哟 前言在当今多核处理器时代并发编程已成为提高应用程序性能的关键。C这一长期以来备受青睐的语言在C11及以后的版本中引入了强大的线程库为开发者提供了丰富的并发编程工具。本文将深入探讨C线程库的奥秘揭示并发编程的复杂性与美妙并通过实际案例带你领略C并发编程的魅力。我们将一起探索线程的创建与管理理解互斥锁、条件变量等同步机制并学习如何利用这些工具解决实际问题。无论是初学者还是经验丰富的开发者都能从中获得宝贵的知识和启示。让我们一起踏上这场并发编程的探索之旅吧 目录 一.线程库
1.1 thread简单类的简单介绍
1.2 线程函数
1.3 线程函数参数
二.线程并发
2.1 原子操作
2.2 mutex的多种互斥量
2.3 lock_guard与unique_lock
2.4 线程交互实例 一.线程库
1.1 thread简单类的简单介绍 在 C11 之前涉及到多线程问题都是和平台相关的比如 windows 和 linux 下各有自己的接 口这使得代码的可移植性比较差 。 C11 中最重要的特性就是对线程进行支持了使得 C 在 并行编程时不需要依赖第三方库 而且在原子操作中还引入了原子类的概念。要使用标准库中的 线程必须包含 thread 头文件。 常见线程函数简介 std::thread 用于创建和管理的线程对象。通过构造函数启动线程并将一个可调用对象如函数、Lambda表达式、函数对象作为参数传递给线程。 std::this_thread::get_id() 返回当前线程的ID。 std::this_thread::sleep_for 使当前线程暂停执行指定的时间长度。 std::this_thread::sleep_until 使当前线程暂停执行直到指定的时间点。 std::mutex 提供基本的互斥锁功能用于保护共享数据免受多线程同时访问。 std::lock_guard 用于管理互斥锁的RAIIResource Acquisition Is Initialization包装器确保在作用域结束时会自动释放锁。 std::unique_lock 提供比std::lock_guard更灵活的互斥锁管理。允许手动锁定和解锁支持条件变量并且可以转移锁的所有权。 std::condition_variable 用于在多线程编程中同步操作。通常与std::mutex一起使用用于等待某个条件成立或通知其他线程条件已经改变。 std::condition_variable_any 与std::condition_variable类似但可以与任何类型的锁满足基本锁概念一起使用。 std::promise 用于在线程之间传递一个值。可以在线程中设置值然后其他线程可以获取这个值。 std::future 用于获取std::promise设置的值。可以查询std::promise设置的结果并检查它是否已准备好。 std::packaged_task 用于将任何可调用对象打包成一个可以异步执行的函数对象。与std::future结合使用可以获取异步操作的结果。 std::async 用于异步执行函数或可调用对象并返回一个std::future对象用于获取异步操作的结果。 这些是C线程库中一些常用的函数和类。它们提供了线程的创建、同步、通信等基本功能是进行多线程编程的基础。 注意 1. 线程是操作系统中的一个概念 线程对象可以关联一个线程用来控制线程以及获取线程的 状态 。 2. 当创建一个线程对象后没有提供线程函数该对象实际没有对应任何线程。 1.2 线程函数 当创建一个线程对象后并且给线程关联线程函数该线程就被启动与主线程一起运行。 线程函数一般情况下可按照以下三种方式提供 1 函数指针 2 ambda表达式 3 函数对象 给大家一个简单例子 #include iostream
using namespace std;
#include thread
void ThreadFunc(int a)
{cout Thread1 a endl;
}
class TF
{
public:void operator()(){cout Thread3 endl;}
};
int main()
{// 线程函数为函数指针thread t1(ThreadFunc, 10);// 线程函数为lambda表达式thread t2([]{cout Thread2 endl; });// 线程函数为函数对象TF tf;thread t3(tf);t1.join();t2.join();t3.join();cout Main thread! endl;return 0;
} 注意thread 类是防拷贝的不允许拷贝构造以及赋值但是可以移动构造和移动赋值即将一个 线程对象关联线程的状态转移给其他线程对象转移期间不意向线程的执行。 可以通过 jionable() 函数判断线程是否是有效的如果是以下任意情况则线程无效 1采用无参构造函数构造的线程对象 2线程对象的状态已经转移给其他线程对象 3线程已经调用 jion 或者 detach 结束 1.3 线程函数参数 线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的 因此即使线程参数为引用类型在 线程中修改后也不能修改外部实参因为 其实际引用的是线程栈中的拷贝而不是外部实参 。 给大家一个具体例子体会 #include thread
void ThreadFunc1(int x)
{x 10;
}
void ThreadFunc2(int* x)
{*x 10;
}
int main()
{int a 10;// 在线程函数中对a修改不会影响外部实参因为线程函数参数虽然是引用方式但其实际
引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout a endl;// 如果想要通过形参改变外部实参时必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a);t2.join();cout a endl;// 地址的拷贝thread t3(ThreadFunc2, a);t3.join();cout a endl;return 0;
} 二.线程并发
2.1 原子操作 多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的那么没问 题因为只读操作不会影响到数据更不会涉及对数据的修改所以所有线程都会获得同样的数 据。但是当一个或多个线程要修改共享数据时就会产生很多潜在的麻烦。 对于传统的并发我们可以使用加锁的方式来保护线程数据安全
std::mutex m;
unsigned long sum 0L;
void fun(size_t num)
{for (size_t i 0; i num; i){m.lock();sum;m.unlock();}
}
int main()
{cout Before joining,sum sum std::endl;thread t1(fun, 10000000);thread t2(fun, 10000000);t1.join();t2.join();cout After joining,sum sum std::endl;return 0;
}
但是缺点也非常明显只要一个线程在对sum的时候其它线程会被阻断影响控制效率而且锁控制不好容易造成死锁。 因此 C11 中引入了原子操作。所谓原子操作即不可被中断的一个或一系列操作 C11 引入 的原子操作类型使得线程间数据的同步变得非常高效。 这是系统自带的原子数据可以保护指定原子数据的线程安全。
例如直接将sum改为原子数据
#include iostream
using namespace std;
#include thread
#include atomic
atomic_long sum{ 0 };
void fun(size_t num)
{for (size_t i 0; i num; i)sum ; // 原子操作
}
int main()
{cout Before joining, sum sum std::endl;thread t1(fun, 1000000);thread t2(fun, 1000000);t1.join();t2.join();cout After joining, sum sum std::endl;return 0;
} 更为普遍的我们可以使用atomic模板定义自己需要的原子类型
atmoicT t; // 声明一个类型为T的原子类型变量t 注意原子类型通常属于 资源型 数据多个线程只能访问单个原子类型的拷贝因此 在 C11 中原子类型只能从其模板参数中进行构造不允许原子类型进行拷贝构造、移动构造以及 operator 等为了防止意外标准库已经将 atmoic 模板类中的拷贝构造、移动构造、赋值运算 符重载默认删除掉了。 2.2 mutex的多种互斥量
在C11中mutex头文件提供了几种不同类型的互斥量mutex以满足不同的同步需求。这些互斥量包括 std::mutex 最基本的互斥量类型提供基本的互斥锁功能。它用于保护共享数据确保同一时间只有一个线程可以访问该数据。 std::recursive_mutex 允许同一个线程多次获得互斥锁的递归互斥量。这对于递归函数或需要多次进入临界区的代码非常有用。 std::timed_mutex 与std::mutex类似但提供了两个额外的成员函数try_lock_for和try_lock_until允许线程尝试在指定的时间范围内获取互斥锁。 std::recursive_timed_mutex 结合了std::recursive_mutex和std::timed_mutex的特性允许递归锁定并且可以设置超时。 注意线程函数调用 lock() 时可能会发生以下三种情况 1如果该互斥量当前没有被锁住则调用线程将该互斥量锁住直到调用 unlock 之前 该线程一直拥有该锁。 2如果当前互斥量被其他线程锁住则当前的调用线程被阻塞住 3如果当前互斥量被当前调用线程锁住则会产生死锁 (deadlock) 线程函数调用 try_lock() 时可能会发生以下三种情况 1如果当前互斥量没有被其他线程占有则该线程锁住互斥量直到该线程调用 unlock 释放互斥量 2如果当前互斥量被其他线程锁住则当前调用线程返回 false 而并不会被阻塞掉 3如果当前互斥量被当前调用线程锁住则会产生死锁 (deadlock) try_lock_for() 接受一个时间范围表示在这一段时间范围之内线程如果没有获得锁则被阻塞住与 std::mutex 的 try_lock() 不同 try_lock 如果被调用时没有获得锁则直接返回 false 如果在此期间其他线程释放了锁则该线程可以获得对互斥量的锁如果超 时即在指定时间内还是没有获得锁则返回 false 。 try_lock_until() 接受一个时间点作为参数在指定时间点未到来之前线程如果没有获得锁则被阻塞住 如果在此期间其他线程释放了锁则该线程可以获得对互斥量的锁如果超时即在指 定时间内还是没有获得锁则返回 false 。 2.3 lock_guard与unique_lock 在多线程环境下如果想要保证某个变量的安全性只要将其设置成对应的原子类型即可即高 效又不容易出现死锁问题。但是有些情况下我们可能需要保证一段代码的安全性那么就只能 通过锁的方式来进行控制。 如下为lock_guard的模板源码
templateclass _Mutex
class lock_guard
{
public:
// 在构造lock_gard时_Mtx还没有被上锁explicit lock_guard(_Mutex _Mtx): _MyMutex(_Mtx){_MyMutex.lock();}
// 在构造lock_gard时_Mtx已经被上锁此处不需要再上锁
lock_guard(_Mutex _Mtx, adopt_lock_t): _MyMutex(_Mtx){}~lock_guard() _NOEXCEPT{_MyMutex.unlock();}lock_guard(const lock_guard) delete;lock_guard operator(const lock_guard) delete;
private:_Mutex _MyMutex;
}; 通过上述代码可以看到 lock_guard 类模板主要是通过 RAII 的方式对其管理的互斥量进行了封 装 在需要加锁的地方只需要用上述介绍的 任意互斥体实例化一个 lock_guard 调用构造函数 成功上锁出作用域前 lock_guard 对象要被销毁调用析构函数自动解锁可以有效避免死锁 问题。 但是缺点依然明显太单一用户没办法对该锁进行控制故c11又提供了uniqu_lock. std::unique_lock是C11标准库中提供的一个模板类用于管理互斥锁mutex。与std::lock_guard类似std::unique_lock也是一个RAIIResource Acquisition Is Initialization包装器用于在作用域结束或异常发生时自动释放锁。不过std::unique_lock比std::lock_guard提供了更多的灵活性和控制。 std::unique_lock的特点包括 灵活的锁管理std::unique_lock允许你在任何时候手动锁定或解锁互斥锁而std::lock_guard在构造时锁定在析构时解锁期间不能手动控制。 条件变量支持std::unique_lock可以与std::condition_variable一起使用用于等待特定的条件成立。这在生产者-消费者模式或其他需要等待特定信号的场景中非常有用。 所有权转移std::unique_lock对象可以通过移动构造函数和移动赋值操作符进行所有权转移。这意味着锁的所有权可以从一个std::unique_lock对象转移到另一个。 锁策略std::unique_lock的构造函数接受一个策略参数可以指定在构造时是否立即锁定互斥锁或者是否采用延迟锁定的策略。 锁的所有权与std::lock_guard不同std::unique_lock可以没有锁的所有权。这意味着它可以被用来包装一个已经锁定的互斥锁而不需要立即解锁。 #include mutex
#include iostream
#include threadstd::mutex mtx; // 创建一个互斥锁
std::condition_variable cv; // 创建一个条件变量void print_thread_id(int id) {std::unique_lockstd::mutex lock(mtx); // 在作用域开始时自动锁定互斥锁// 使用条件变量等待cv.wait(lock, []{ return true; }); // 此处仅为示例实际应用中应有具体的条件判断std::cout Thread # id \n;// 作用域结束时lock对象被销毁自动解锁互斥锁
}int main() {// 假设有多个线程调用print_thread_id// 每个线程都会在print_thread_id函数中安全地访问共享资源
}2.4 线程交互实例
#include thread
#include mutex
#include condition_variable
void two_thread_print()
{std::mutex mtx;condition_variable c;int n 100;bool flag true;thread t1([](){int i 0;while (i n){unique_lockmutex lock(mtx);c.wait(lock, []()-bool{return flag; });cout i endl;flag false;i 2; // 偶数c.notify_one();}});thread t2([](){int j 1;while (j n){unique_lockmutex lock(mtx);c.wait(lock, []()-bool{return !flag; });cout j endl;j 2; // 奇数flag true;c.notify_one();}});t1.join();t2.join();
}int main()
{two_thread_print();return 0;
}
这段代码展示了如何在两个线程之间交替打印奇数和偶数。这里使用了std::mutex、std::unique_lock和std::condition_variable来实现线程间的同步。
让我们逐步解释代码的工作原理 函数two_thread_print 此函数设置了一个多线程打印的情景。它创建了一个互斥锁mtx和一个条件变量c并初始化一个整型变量n为100以及一个布尔型变量flag为true。 线程t1 t1是一个Lambda表达式创建的线程它打印偶数。i初始化为0然后进入一个循环直到i小于n。使用unique_lockmutex锁定互斥锁mtx然后调用c.wait等待条件变量c的通知。c.wait的第二个参数是一个Lambda表达式它返回flag的值。这意味着t1线程将在flag为true时继续执行。当t1获得通知并继续执行时它打印当前的i值然后将flag设置为false表示下一个应该打印奇数。i增加2然后调用c.notify_one通知另一个线程。 线程t2 t2也是一个Lambda表达式创建的线程它打印奇数。j初始化为1然后进入一个循环直到j小于n。使用unique_lockmutex锁定互斥锁mtx然后调用c.wait等待条件变量c的通知。c.wait的第二个参数是一个Lambda表达式它返回!flag的值。这意味着t2线程将在flag为false时继续执行。当t2获得通知并继续执行时它打印当前的j值然后将flag设置为true表示下一个应该打印偶数。j增加2然后调用c.notify_one通知另一个线程。 主线程 主线程创建t1和t2线程然后分别调用它们的join方法等待它们完成。 总结 这个程序的关键在于flag变量和条件变量c的使用。flag用于控制哪个线程应该打印而条件变量c用于线程间的同步。当一个线程打印一个数字并改变flag的值后它通过notify_one通知另一个线程。另一个线程等待条件变量c的通知并在flag的值改变后继续执行。
这个程序展示了如何在C中使用线程和条件变量来实现复杂的线程间同步。 好啦到这里这篇文章就结束啦关于实例代码中我写了很多注释如果大家还有不懂得可以评论区或者私信我都可以哦 感谢大家的阅读我还会持续创造网络编程相关内容的记得点点小爱心和关注哟