创建和等待多个线程
基础示例
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
using namespace std;
void myprint(int num )
{
cout << "func = " << num << " threadid = " << this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return;
}
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
vector<thread>mythreads;
//创建十个线程,执行函数
for (int i = 0; i < 10; i++)
{
#if 0
thread t(myprint,i);
mythreads.push_back(t);
#else
mythreads.push_back(thread(myprint, i)); //不能用以上写法,因为以上是一个临时对象,结束之后会销毁。 vector内容在push时,会调用拷贝构造函数,如果是一个临时变量会调用移动构造
#endif
}
//for(auto it: mythreads) //这样好像不行,求评论区指出正确写法。
//{
// it.join();
//}
for (int i = 0; i < 10; i++)
{
mythreads[i].join();
}
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}
数据共享保护案例代码
产生竞争的代码示例
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
#include <list>
using namespace std;
class TA
{
public:
int m_i; //mutable可以修改变量的值
list<int> m_list;
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this<< " threadid = "<< this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" <<this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
void thread_work()
{
//把收到的消息放到队列线程里。
cout << "TA thread_work " << this << " threadid = " << this_thread::get_id() << endl;
for (int i = 0; i < 100000;i++)
{
m_list.push_back(i);
cout << "thread_work执行,插入一个元素" << i << endl;
}
}
void therad_out()
{
cout << "TA therad_out " << this << " threadid = " << this_thread::get_id() << endl;
for (int i = 0; i < 100000; i++)
{
if (!m_list.empty())
{
int command = m_list.front();
m_list.pop_front();
cout << "threadout执行,拿到的元素是" << command << endl;
}
else
cout << "threadout 执行,但是消息队列是空的"<< i << endl;
}
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl<<endl;
TA ta(10);
thread myoutobj(&TA::therad_out, ref(ta) ); //第二个参数使用引用可以保证使用同一个对象。
thread myworkbj(&TA::thread_work, &ta); //第二个参数使用引用可以保证使用同一个对象。
myoutobj.join();
myworkbj.join();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}
使用互斥量解决竞争(mutex)
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
#include <list>
using namespace std;
class TA
{
public:
int m_i; //mutable可以修改变量的值
list<int> m_list;
mutex m_mutex;
TA(int i) :m_i(i) // 列表初始化来初始化对象。 explicit可以禁止隐式转换
{
cout << "TA()构造函数被执行" << this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA& ta) :m_i(ta.m_i)
{
cout << "TA拷贝构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
TA(const TA&& ta) :m_i(ta.m_i)
{
//m_i = 0;
//ta.m_i = -100;
cout << "TA移动构造函数" << this << " threadid = " << this_thread::get_id() << endl;
}
void thread_work()
{
//把收到的消息放到队列线程里。
cout << "TA thread_work " << this << " threadid = " << this_thread::get_id() << endl;
for (int i = 0; i < 100000; i++)
{
m_mutex.lock();
m_list.push_back(i);
cout << "thread_work执行,插入一个元素" << i << endl;
m_mutex.unlock();
}
}
void therad_out()
{
cout << "TA therad_out " << this << " threadid = " << this_thread::get_id() << endl;
int command = 0;
for (int i = 0; i < 100000; i++)
{
bool ret = outMsgProc(command);
if (ret == true)
{
cout << "threadout执行,拿到的元素是" << command << endl;
}
else
{
cout << "threadout 执行,但是消息队列是空的" << i << endl;
}
}
}
bool outMsgProc(int& command)
{
m_mutex.lock();
if (!m_list.empty())
{
int command = m_list.front();
m_list.pop_front();
m_mutex.unlock();
return true;
}
m_mutex.unlock();
return false;
}
~TA()
{
cout << "TA析构函数" << this << " threadid = " << this_thread::get_id() << endl;
}
};
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl << endl;
TA ta(10);
thread myoutobj(&TA::therad_out, ref(ta)); //第二个参数使用引用可以保证使用同一个对象。
thread myworkbj(&TA::thread_work, &ta); //第二个参数使用引用可以保证使用同一个对象。
myoutobj.join();
myworkbj.join();
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}
智能锁(lock_guard<mutex> )
bool outMsgProc(int& command)
{
std::lock_guard<mutex> guard(m_mutex); //智能锁,出去作用域会自动释放,使用之后就不能用lock和unlock了。
//m_mutex.lock();
if (!m_list.empty())
{
int command = m_list.front();
m_list.pop_front();
//m_mutex.unlock();
return true;
}
//m_mutex.unlock();
return false;
}
死锁问题
死锁是并发编程中的一个经典问题,发生在两个或多个线程在等待对方释放资源时永远阻塞的情况。死锁会导致程序无法继续执行,因为涉及的线程都无法继续向前推进。死锁通常由以下四个必要条件同时出现引起:
互斥条件:资源不能被共享,只能由一个线程占用。
占有并等待:线程已经持有至少一个资源,并且等待获取其他线程持有的资源。
不可剥夺:资源不能被强制从线程中剥夺,只能由线程自己释放。
循环等待:存在一个线程循环链,链中的每个线程都在等待下一个线程所持有的资源。
死锁的预防和解决策略
资源有序分配:为资源分配一个全局顺序,所有线程都必须按照这个顺序来请求资源。
避免占有并等待:线程在请求资源前不持有任何资源,或者一次性请求所有需要的资源。
资源剥夺:允许系统在必要时从线程中剥夺资源。
超时机制:线程在请求资源时设置超时,如果在超时时间内没有获取到资源,就释放已持有的资源并重试。
死锁检测与恢复:系统定期检测死锁,并采取恢复措施,如回滚事务或重启线程。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mutex1;
std::mutex mutex2;
void thread1() {
mutex1.lock();
std::cout << "Thread 1: Holding mutex1, waiting for mutex2." << std::endl;
mutex2.lock();
std::cout << "Thread 1: Holding both mutexes." << std::endl;
mutex2.unlock();
mutex1.unlock();
}
void thread2() {
mutex2.lock();
std::cout << "Thread 2: Holding mutex2, waiting for mutex1." << std::endl;
mutex1.lock();
std::cout << "Thread 2: Holding both mutexes." << std::endl;
mutex1.unlock();
mutex2.unlock();
}
int main() {
std::thread t1(thread1);
std::thread t2(thread2);
t1.join();
t2.join();
return 0;
}
在这个例子中,thread1
和 thread2
都试图先锁定 mutex1
,然后锁定 mutex2
。如果两个线程几乎同时运行,它们将相互等待对方释放资源,从而导致死锁。
智能锁(std::lock)
std::lock
是 C++11 引入的一个函数,用于一次性锁定多个互斥锁(mutexes),从而避免死锁。它确保所有提供的互斥锁都被锁定,或者在遇到异常时,已经锁定的互斥锁会被解锁,这通过 RAII(资源获取即初始化)风格保证。
#include <mutex>
#include <thread>
std::mutex mtx1, mtx2;
void threadSafeFunction() {
std::lock(mtx1, mtx2); // 一次性锁定两个互斥锁,要么同时锁,要么同时不锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
// 临界区代码,现在可以安全地访问共享资源
// 无需手动解锁,lock1 和 lock2 在作用域结束时自动解锁
}
在这个例子中,std::lock_guard
的 std::adopt_lock
参数告诉 std::lock_guard
,互斥锁已经被锁定,并且 std::lock_guard
应该在析构时解锁它们。
注意事项
避免死锁:使用
std::lock
可以减少死锁的风险,因为它确保所有互斥锁同时被锁定或解锁。异常安全:
std::lock
与std::lock_guard
结合使用,可以确保即使在抛出异常的情况下,互斥锁也能被正确解锁。性能考虑:虽然
std::lock
提供了方便的一次性锁定多个互斥锁的能力,但它可能比单独锁定每个互斥锁有更高的性能开销。在性能敏感的应用中,应谨慎使用。可扩展性:
std::lock
可以处理任意数量的互斥锁,这使得它在需要锁定多个资源时非常灵活。
通过使用 std::lock
和 std::lock_guard
,你可以编写更安全、更易于维护的并发代码。(谨慎使用)
智能锁(unique_lock)
std::unique_lock
是 C++11 引入的一个类模板,它提供了一种灵活的方式来管理互斥量(mutex)。与 std::lock_guard
相比,std::unique_lock
更加灵活,允许在不同的作用域和不同的锁定策略之间进行选择。以下是 std::unique_lock
的一些主要特点和用法:
特点
灵活性:
std::unique_lock
可以在构造时选择是否锁定互斥量,支持手动锁定和解锁,允许条件变量的等待,以及在等待条件变量时自动解锁和重新锁定。避免死锁:由于
std::unique_lock
的 RAII 特性,确保在作用域结束时自动解锁,降低了因忘记解锁而引起死锁的风险。可以延迟锁定:你可以在构造
unique_lock
时不锁定互斥量,并在后面需要时再手动锁定。可移动:
std::unique_lock
是可移动的,但不可复制,这使得它可以在线程间安全地转移锁的所有权。
adopt_lock
- 延迟锁定:可以在构造时选择不锁定互斥量,并在需要时手动锁定。
std::adopt_lock
是 C+++11 引入的一个标签类型,用于指示 std::unique_lock
或 std::lock_guard
在构造时不要尝试去锁定传入的互斥锁(mutex),因为互斥锁已经被当前线程锁定。这个标签主要用于在已经手动锁定互斥锁的情况下,将互斥锁的管理权转移给 std::unique_lock
或 std::lock_guard
对象,以利用它们的异常安全保证(RAII)。
使用 std::adopt_lock
时,你必须确保在构造 std::unique_lock
或 std::lock_guard
之前,互斥锁已经被当前线程锁定。这通常用于避免在已经手动锁定互斥锁的情况下,再次尝试锁定互斥锁,从而提高效率。
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void print_thread_id(int id) {
mtx.lock(); // 手动锁定互斥锁
std::lock_guard<std::mutex> lck(mtx, std::adopt_lock); // 告诉 lock_guard 互斥锁已经被锁定
std::cout << "thread #" << id << '\n';
mtx.unlock(); // 手动解锁互斥锁
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_thread_id, i + 1);
for (auto& th : threads) th.join();
return 0;
}
try_to_lock
- 递归锁定:允许同一个线程多次锁定同一个互斥量。
std::unique_lock
提供了一种灵活的方式来管理互斥锁(mutex),它允许你尝试锁定互斥锁而不必真正锁定它。try_to_lock
是 std::unique_lock
的一个构造参数选项,用于实现这种尝试锁定的行为。
当你使用 std::try_to_lock
作为 std::unique_lock
的构造函数参数时,互斥锁不会立即被锁定。相反,std::unique_lock
会尝试锁定互斥锁,如果互斥锁已经被其他线程锁定,则不会阻塞调用线程,而是立即返回。这允许你实现非阻塞的锁定机制。
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void threadSafeFunction() {
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 如果成功锁定,执行临界区代码
std::cout << "Thread " << std::this_thread::get_id() << " has the lock." << std::endl;
} else {
// 如果未能锁定,可以执行其他操作或重试
std::cout << "Thread " << std::this_thread::get_id() << " could not get the lock." << std::endl;
}
}
int main() {
std::thread t1(threadSafeFunction);
std::thread t2(threadSafeFunction);
t1.join();
t2.join();
return 0;
}
defer_lock
在 C++ 中,std::unique_lock
提供了一种灵活的互斥锁管理方式,它允许延迟锁定(defer locking)。std::defer_lock
是 std::unique_lock
的构造函数的一个可选参数,用于指示 std::unique_lock
的实例在初始化时不立即锁定互斥锁,而是将其保持在未锁定状态。这使得开发者可以在之后的某个时刻显式地调用 lock
方法来获取锁。
使用 std::defer_lock
的优点包括:
- 延迟锁定:允许开发者在构造
std::unique_lock
实例后,根据需要决定何时锁定互斥锁。 - 避免死锁:通过延迟锁定,可以减少在获取多个锁时发生死锁的风险。
- 灵活性:提供了在尝试锁定之前执行其他操作的能力,例如检查条件或执行初始化。
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void threadSafeFunction() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟锁定
// 执行一些不需要互斥的操作
// ...
// 现在锁定互斥锁
lock.lock();
// 临界区代码,现在可以安全地访问共享资源
std::cout << "Thread " << std::this_thread::get_id() << " has the lock." << std::endl;
// 离开作用域时自动解锁
}
int main() {
std::thread t1(threadSafeFunction);
std::thread t2(threadSafeFunction);
t1.join();
t2.join();
return 0;
}
release
std::unique_lock
的 release
成员函数用于释放与互斥锁(mutex)的关联,但不解锁互斥锁。当你调用 release
方法后,std::unique_lock
实例将不再管理互斥锁,这意味着它不会在析构时解锁互斥锁。这允许你将互斥锁的控制权显式地转移给其他 std::unique_lock
实例或手动管理互斥锁。
使用 release
方法后,你需要确保互斥锁在不再需要时被正确解锁,因为 std::unique_lock
不再负责解锁操作。
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
void transferLock() {
std::unique_lock<std::mutex> lock(mtx);
// 临界区代码
std::cout << "Thread " << std::this_thread::get_id() << " has the lock." << std::endl;
// 释放互斥锁的控制权,但不解锁
lock.release();
}
void manualUnlock() {
// 直接使用已经释放的互斥锁
mtx.unlock();
std::cout << "Thread " << std::this_thread::get_id() << " manually unlocked the mutex." << std::endl;
}
int main() {
std::thread t1(transferLock);
std::thread t2(manualUnlock);
t1.join();
t2.join();
return 0;
}
单例设计模式分析与解决
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
#include <list>
using namespace std;
class MyCls
{
public:
// 提供一个全局访问点,用于获取类的唯一实例
static MyCls* m_inst;
static MyCls* instance()
{
if (m_inst == nullptr)
{
m_inst = new MyCls();
static CGar c1;
}
return m_inst;
}
class CGar
{
public:
~CGar()
{
if (MyCls::m_inst)
{
delete MyCls::m_inst;
MyCls::m_inst = NULL;
cout << "单例数据被释放了" << endl;
}
}
};
static MyCls* instance1()
{
static MyCls* inst = new MyCls;
return inst;
}
};
MyCls* MyCls::m_inst = NULL;
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl << endl;
cout << MyCls::instance() << endl;
cout << MyCls::instance() << endl;
cout << MyCls::instance1() << endl;
cout << MyCls::instance1() << endl;
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}
多线程时问题与解决
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
#include <list>
using namespace std;
class MyCls
{
public:
// 提供一个全局访问点,用于获取类的唯一实例
static MyCls* m_inst;
static MyCls* instance()
{
if (m_inst == nullptr)
{
this_thread::sleep_for(std::chrono::microseconds(1000));
static CGar c1;
m_inst = new MyCls();
}
return m_inst;
}
class CGar
{
public:
~CGar()
{
if (MyCls::m_inst)
{
delete MyCls::m_inst;
MyCls::m_inst = NULL;
cout << "单例数据被释放了" << endl;
}
}
};
static MyCls* instance1()
{
this_thread::sleep_for(std::chrono::microseconds(1000));
//方式一
//static MyCls* inst = new MyCls; //在C++11之后,这种写法是安全的
//return inst;
//方式二
static MyCls inst;
return &inst;
}
};
MyCls* MyCls::m_inst = NULL;
void mythread()
{
cout << "mythread start" << endl;
MyCls* me = MyCls::instance1();
cout << "me = " << me;
cout << "mythread end" << endl;
}
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl << endl;
//cout << MyCls::instance() << endl;
//cout << MyCls::instance() << endl;
std::thread obj1(mythread);
std::thread obj2(mythread);
obj1.join();
obj2.join();
//cout << MyCls::instance1() << endl;
//cout << MyCls::instance1() << endl;
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}
call_once
// ConsoleApplication10.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <thread>
#include <mutex>
#include <cstdint>
#include <list>
using namespace std;
std::mutex g_mut;
std::once_flag g_flag;
class MyCls
{
public:
// 提供一个全局访问点,用于获取类的唯一实例
static MyCls* m_inst;
static MyCls* instance()
{
if (m_inst != nullptr)
return m_inst; //使用双检索进行判断。
std::unique_lock<std::mutex> mymut(g_mut); //自动加锁,出作用域自动释放。
if (m_inst == nullptr)
{
this_thread::sleep_for(std::chrono::microseconds(1000));
static CGar c1;
m_inst = new MyCls();
}
return m_inst;
}
class CGar
{
public:
~CGar()
{
if (MyCls::m_inst)
{
delete MyCls::m_inst;
MyCls::m_inst = NULL;
cout << "单例数据被释放了" << endl;
}
}
};
static MyCls* instance1()
{
this_thread::sleep_for(std::chrono::microseconds(1000));
//方式一
//static MyCls* inst = new MyCls; //在C++11之后,这种写法是安全的
//return inst;
//方式二
static MyCls inst;
return &inst;
}
};
MyCls* MyCls::m_inst = NULL;
void mythread()
{
cout << "mythread start" << endl;
MyCls* me = MyCls::instance();
cout << "me = " << me;
cout << "mythread end" << endl;
}
int main()
{
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl << endl;
//cout << MyCls::instance() << endl;
//cout << MyCls::instance() << endl;
#if 1
std::thread obj1(mythread);
std::thread obj2(mythread);
obj1.join();
obj2.join();
#endif
//std::call_once(std::once_flag::once_flag,mythread);//call_once保证函数只调用一次,具备互斥量能力
std::call_once(g_flag, mythread);
std::call_once(g_flag, mythread);
//cout << MyCls::instance1() << endl;
//cout << MyCls::instance1() << endl;
cout << "=========================================== " << " threadid = " << this_thread::get_id() << endl;
return 0;
}