欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > C++ 线程

C++ 线程

2024/10/25 2:19:27 来源:https://blog.csdn.net/gopher9511/article/details/141158150  浏览:    关键词:C++ 线程
  • C++ 线程
  • 成员函数
  • 线程创建
    • 线程分离
    • 批量创建
  • try_lock/unclok
  • lock_guard
  • lock_guard
  • 死锁
    • 递归锁 (unique_lock /recursive_mutex)
  • 条件变量(condition variable)

C++ 线程

成员函数

1)构造函数:std::mutex 不允许拷贝构造,也不允许 move 拷贝,最初产生的mutex 对象是处于 unlocked 状态的。
2)lock():调用线程将锁住该互斥量,线程调用该函数会发生以下 3 种情况:(a)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用unlock 之前,该线程一直拥有该锁。(b)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(c)如果当前互斥量被当前调用线程锁住,则会产生死锁。
3)unlock():解锁,释放对互斥量的所有权。
4)try_lock():尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞,线程调用该函数会出现下面 3 种情况:(a)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。(b)如果当前互斥量被其他线程锁住,false,而并不会被阻塞掉。(c)如果当前互斥量被当前调用线程锁住,则会产生死锁。

线程创建

C++线程的启动,只需要#include 即可。线程对象的创建,意味着线
程的开始。

//todo 线程
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;// 定义一个函数,该函数将在新线程中运行
void func()
{// 输出当前线程的IDcout << "thread id:" << this_thread::get_id() << endl;cout << "线程运行" << endl;// 让当前线程休眠10秒this_thread::sleep_for(chrono::seconds(10));
}int main()
{// 输出主线程的IDcout << "main thread id:" << this_thread::get_id() << endl;// 创建一个新线程,运行func函数thread t(func);// 等待新线程完成(即等待func函数执行完毕)t.join();cout << "线程运行结束" << endl;return 0;
}
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;// 定义一个函数,该函数将在新线程中运行
void func(int & n,string & c)
{for (int i = 0; i < n; ++i) {cout<<c<<endl;}// 修改传入的参数n和c的值n = 10;c = "aa";}int main()
{int n=5;string str="cccc";// 创建一个新线程t,并传入函数func和参数n、str的引用//std::ref 是 C++ 标准库中的一个函数模板,位于 <functional> 头文件中。// 它的作用是创建一个 std::reference_wrapper 类型的对象,// 该对象可以用来传递引用,而不是传递值。thread t(func, std::ref(n), std::ref(str));t.join();cout<< n<<" "<<str<<endl;//10 aareturn 0;
}

线程分离

#if 1
//todo 线程
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;// 定义一个函数,该函数将在新线程中运行
void func(int  n,string c)
{for (int i = 0; i < n; ++i) {cout<<c<<endl;}}int main()
{int n=5;string str="cccc";// 创建一个新线程t,并传入函数func和参数n、strthread t(func, n, str);// 将线程t设置为分离状态,使其在后台运行t.detach();// 检查线程t是否仍然可连接(即是否仍在运行)if (t.joinable()) {cout << "等待线程执行完毕" << endl;// 如果线程仍可连接,则等待其执行完毕t.join();}return 0;
}# endif

批量创建

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>using namespace std;// 定义一个宏,表示要创建的线程数量
#define N 1000// 定义一个函数,该函数将在新线程中运行
void foo(int id)
{cout << "Thread " << id << " started. ID: " << this_thread::get_id() << endl;// 让当前线程休眠一段时间,模拟工作负载this_thread::sleep_for(chrono::seconds(1));cout << "Thread " << id << " finished." << endl;
}int main()
{// 创建一个存储线程对象的向量vector<thread> threads;cout<<"thread 大小:"<< sizeof(thread)<<endl;//8// 预分配向量空间,避免多次内存分配// reserve 函数用于预分配内存空间,使得向量的容量(capacity)至少为指定的值。这样,在向向量中添加元素时,如果元素数量不超过预分配的容量,就不会触发内存重新分配,从而提高性能。threads.reserve(N);// 循环创建并启动N个线程for (int i = 0; i < N; ++i){// 创建一个新线程,并执行foo函数,传递线程编号作为参数//emplace_back 函数用于在向量的末尾直接构造一个元素。它通过可变参数模板(variadic templates)接受构造函数的参数,并在向量的末尾直接调用元素的构造函数,从而避免复制或移动操作。threads.emplace_back(foo, i);}// 等待所有线程执行完毕for (auto& th : threads){if (th.joinable()){th.join();}}cout << "All threads finished." << endl;return 0;
}

//todo 线程同步#include <iostream>
#include <mutex>
#include <thread>
using namespace std;volatile int counter(0); //volatile 关键字在 C++ 中用于告诉编译器,某个变量的值可能会在程序的控制之外被改变,因此编译器不应该对该变量进行优化,到寄存器。mutex mtx;//锁void increase10Ktime()
{for(int i=0; i<10000; i++){mtx.lock();//加锁counter++;mtx.unlock();//解锁}
}
int main()
{thread ths[10];for(int i=0; i<10; i++){ths[i] = thread(increase10Ktime);}for(auto &th:ths)th.join();cout<<"after successful increase :"<<counter<<endl;return 0;
}

try_lock/unclok


# if 1
//todo 线程同步 try_lock/unclok#include <iostream>
#include <mutex>
#include <thread>
using namespace std;volatile int counter(0); //volatile 关键字在 C++ 中用于告诉编译器,某个变量的值可能会在程序的控制之外被改变,因此编译器不应该对该变量进行优化,到寄存器。mutex mtx;//锁void increase10Ktime()
{for(int i=0; i<10000; i++){mtx.try_lock();counter++;mtx.unlock();//解锁}
}
int main()
{thread ths[10];for(int i=0; i<10; i++){ths[i] = thread(increase10Ktime);}for(auto &th:ths)th.join();cout<<"after successful increase :"<<counter<<endl;return 0;
}# endif

lock_guard

在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。
在 lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,
因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的
Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常
处理代码。与 Mutex RAII 相关,方便线程对互斥量上锁。using a local lock_guard to lock
mtx guarantees unlocking on destruction / exception:

引例:

//todo RAII
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 声明一个互斥锁对象
mutex mtx;// 函数:检查一个整数是否为偶数,并输出结果
void printEven(int i)
{if( i % 2 == 0)  // 如果i是偶数cout << i << " is even" << endl;  // 输出i是偶数elsethrow logic_error("not even");  // 如果i不是偶数,抛出一个逻辑错误异常
}// 函数:打印线程ID,并处理可能的异常
void printThreadId(int id)
{try{mtx.lock();  // 锁定互斥锁,确保线程安全printEven(id);  // 调用printEven函数,检查并输出id是否为偶数mtx.unlock();  // 解锁互斥锁}catch(logic_error & ){  // 捕获逻辑错误异常cout << "exception caught" << endl;  // 输出异常信息}
}int main()
{thread ths[10];  // 声明一个包含10个线程对象的数组// 创建10个线程for(int i = 0; i < 10; i++){ths[i] = thread(printThreadId, i + 1);  // 启动线程,调用printThreadId函数,参数为i+1}// 等待所有线程完成for(auto & th : ths)th.join();  // 主线程等待每个子线程完成return 0;
}获取锁后,抛出异常,就不会解锁

lock_guard

lock_guard 的使用体现了 RAII(Resource Acquisition Is Initialization)思想。RAII 是 C++ 中的一种编程范式,核心思想是通过对象的生命周期来管理资源,确保资源在对象构造时获取,在对象析构时释放。RAII 的核心概念资源获取即初始化:在对象构造时获取资源,在对象析构时释放资源。自动管理资源:通过对象的生命周期自动管理资源,避免资源泄漏和手动管理资源的复杂性。lock_guard 如何体现 RAII构造时获取资源:lock_guard 在其构造函数中锁定互斥锁。析构时释放资源:lock_guard 在其析构函数中解锁互斥锁。这意味着当 lock_guard 对象被创建时,互斥锁会被自动锁定;当 lock_guard 对象离开其作用域(例如函数返回或异常抛出)时,互斥锁会被自动解锁。

# if 1
//todo RAII
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;// 声明一个互斥锁对象
mutex mtx;// 函数:检查一个整数是否为偶数,并输出结果
void printEven(int i)
{if( i % 2 == 0)  // 如果i是偶数cout << i << " is even" << endl;  // 输出i是偶数elsethrow logic_error("not even");  // 如果i不是偶数,抛出一个逻辑错误异常
}// 函数:打印线程ID,并处理可能的异常
void printThreadId(int id)
{try{lock_guard<mutex> lck(mtx);//它在其构造函数中锁定互斥锁,并在其析构函数中解锁互斥锁。这样可以确保在离开作用域时自动释放锁,避免忘记解锁导致的死锁问题。printEven(id);  // 调用printEven函数,检查并输出id是否为偶数}catch(logic_error & ){  // 捕获逻辑错误异常cout << "exception caught" << endl;  // 输出异常信息}
}int main()
{thread ths[10];  // 声明一个包含10个线程对象的数组// 创建10个线程for(int i = 0; i < 10; i++){ths[i] = thread(printThreadId, i + 1);  // 启动线程,调用printThreadId函数,参数为i+1}// 等待所有线程完成for(auto & th : ths)th.join();  // 主线程等待每个子线程完成return 0;
}# endif
输出
exception caught
2 is even
exception caught
10 is even
exception caught
6 is even
exception caught
8 is even
exception caught
4 is even

死锁

死锁的原因是,container 试图多次去获取锁己获得的锁。std::recursive_mutex 允
许多次获取相同的 mutex。

死锁:

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
using namespace std;// 模板类:容器,用于存储元素并提供线程安全的添加和访问方法
template <typename T>
class container
{
public:// 添加单个元素到容器中void add(T element){_mtx.lock();  // 锁定互斥锁,确保线程安全_elements.push_back(element);  // 将元素添加到向量中_mtx.unlock();  // 解锁互斥锁}// 添加多个元素到容器中void addrange(int num, ...){va_list arguments;  // 声明一个可变参数列表va_start(arguments, num);  // 初始化可变参数列表for (int i = 0; i < num; i++){_mtx.lock();  // 锁定互斥锁,确保线程安全add(va_arg(arguments, T));  // 从可变参数列表中获取一个元素并添加到容器中_mtx.unlock();  // 解锁互斥锁}va_end(arguments);  // 结束可变参数列表}// 输出容器中的所有元素void dump(){_mtx.lock();  // 锁定互斥锁,确保线程安全for(auto e : _elements)cout << e << endl;  // 输出每个元素_mtx.unlock();  // 解锁互斥锁}private:mutex _mtx;  // 互斥锁,用于保护共享数据vector<T> _elements;  // 存储元素的向量
};// 线程函数:向容器中添加随机数
void func(container<int>& cont)
{cont.addrange(3, rand(), rand(), rand());  // 向容器中添加3个随机数
}int main()
{srand((unsigned int)time(0));  // 初始化随机数生成器container<int> cont;  // 创建一个存储整数的容器// 创建3个线程,每个线程调用func函数向容器中添加随机数thread t1(func, ref(cont));thread t2(func, ref(cont));thread t3(func, ref(cont));// 等待所有线程完成t1.join();t2.join();t3.join();cont.dump();  // 输出容器中的所有元素return 0;
}

递归锁 (unique_lock /recursive_mutex)

recursive_mutex _mtx;

recursive_mutex 是 C++ 标准库中的一种互斥锁,允许同一个线程多次锁定同一个互斥锁,而不会导致死锁。这与普通的 mutex 不同,普通的 mutex 在同一个线程中多次锁定会导致死锁。

unique_lock<recursive_mutex> lck(_mtx);

unique_lock 是 C++11 引入的一种智能锁,用于简化互斥锁的管理。与 lock_guard 类似,unique_lock 也提供了 RAII 风格的锁管理,但它更加灵活,支持延迟锁定、锁的所有权转移和手动解锁等操作。

改进:

include <iostream>
#include <mutex>
#include <thread>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
using namespace std;// 模板类:容器,用于存储元素并提供线程安全的添加和访问方法
template <typename T>
class container
{
public:// 添加单个元素到容器中void add(T element){unique_lock<recursive_mutex> lck(_mtx);_elements.push_back(element);  // 将元素添加到向量中}// 添加多个元素到容器中void addrange(int num, ...){va_list arguments;  // 声明一个可变参数列表va_start(arguments, num);  // 初始化可变参数列表for (int i = 0; i < num; i++){unique_lock<recursive_mutex> lck(_mtx);add(va_arg(arguments, T));  // 从可变参数列表中获取一个元素并添加到容器中}va_end(arguments);  // 结束可变参数列表}// 输出容器中的所有元素void dump(){unique_lock<recursive_mutex> lck(_mtx);//unique_lock 是 C++11 引入的一种智能锁,用于简化互斥锁的管理。与 lock_guard 类似,unique_lock 也提供了 RAII 风格的锁管理,但它更加灵活,支持延迟锁定、锁的所有权转移和手动解锁等操作。for(auto e : _elements)cout << e << endl;  // 输出每个元素}private:recursive_mutex _mtx;  // recursive_mutex 是 C++ 标准库中的一种互斥锁,允许同一个线程多次锁定同一个互斥锁,而不会导致死锁。这与普通的 mutex 不同,普通的 mutex 在同一个线程中多次锁定会导致死锁。vector<T> _elements;  // 存储元素的向量
};// 线程函数:向容器中添加随机数
void func(container<int>& cont)
{cont.addrange(3, rand(), rand(), rand());  // 向容器中添加3个随机数
}int main()
{srand((unsigned int)time(0));  // 初始化随机数生成器container<int> cont;  // 创建一个存储整数的容器// 创建3个线程,每个线程调用func函数向容器中添加随机数thread t1(func, ref(cont));thread t2(func, ref(cont));thread t3(func, ref(cont));// 等待所有线程完成t1.join();t2.join();t3.join();cont.dump();  // 输出容器中的所有元素return 0;
}
输出
6334
18467
41
6334
18467
41
6334
18467
41

条件变量(condition variable)

条件变量(condition variable)是利用线程间共享的全局变量进行同步的一种机
制,

主要包括两个动作:

一个线程等待某个条件为真,而将自己挂起;

另一个线程使的条件成立,并通知等待的线程继续。

为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。

C++11 中引入了条件变量,其相关内容均在<condition_variable>中。

这里主要介绍 std::condition_variable 类。

条件变量 std::condition_variable 用于多线程之间的通信,它可以阻塞一个或同时阻塞多个线程。

std::condition_variable 需 要 与 std::unique_lock 配 合 使 用 。

std::condition_variable 效果上相当于包装了 pthread 库中的 pthread_cond_*()系列的函数。

当 std::condition_variable 对象的某个wait函数被调用的时候 , 它使用std::unique_lock(通过 std::mutex)来锁住当前线程。
当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒
当前线程。


# if 1
//todo 条件变量(condition variable)
/*
10 个线程,争相去打印,传入的 id,第一个线程但是获得锁的,但为条件不满足,
采用条件变量出让锁,等待条件,其它线程亦是如此。延时后的主要线程,获得锁,将
条件置为真,并能过条件变量唤醒所有等待在条件变量上的 10 个线程。此时 10 个线
程再次争相去获取锁,然后,判断条件为真,然后打印,释放锁给其它线程。
*/
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;// 全局变量,用于指示线程是否可以开始工作
bool ready = false;// 互斥锁,用于保护共享变量
mutex mtx;// 条件变量,用于线程间的同步
condition_variable cv;// 线程函数:打印线程ID
void printId(int id) {unique_lock<mutex> lck(mtx);  // 创建unique_lock并锁定互斥锁while (!ready) {  // 检查ready变量是否为truecv.wait(lck);  // 如果ready为false,则线程进入等待状态,并解锁互斥锁}cout << "id= " << id << endl;  // 打印线程ID
}  // unique_lock在离开作用域时自动解锁// 函数:通知所有线程开始工作
void go() {unique_lock<mutex> lck(mtx);  // 创建unique_lock并锁定互斥锁ready = true;  // 设置ready变量为true,表示线程可以开始工作cv.notify_all();  // 通知所有等待的线程
}  // unique_lock在离开作用域时自动解锁int main() {thread ths[10];  // 声明一个包含10个线程对象的数组// 创建10个线程,每个线程调用printId函数,参数为线程IDfor (int i = 0; i < 10; ++i) {ths[i] = thread(printId, i);}// 主线程休眠5秒this_thread::sleep_for(chrono::seconds(5));// 通知所有线程开始工作go();// 等待所有线程完成for (auto &t : ths) {t.join();}return 0;
}# endif

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com