C++并发与多线程(创建多个线程)

发布于:2024-12-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

创建和等待多个线程

基础示例

// 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;
	}

死锁问题

死锁是并发编程中的一个经典问题,发生在两个或多个线程在等待对方释放资源时永远阻塞的情况。死锁会导致程序无法继续执行,因为涉及的线程都无法继续向前推进。死锁通常由以下四个必要条件同时出现引起:

  1. 互斥条件:资源不能被共享,只能由一个线程占用。

  2. 占有并等待:线程已经持有至少一个资源,并且等待获取其他线程持有的资源。

  3. 不可剥夺:资源不能被强制从线程中剥夺,只能由线程自己释放。

  4. 循环等待:存在一个线程循环链,链中的每个线程都在等待下一个线程所持有的资源。

死锁的预防和解决策略

  1. 资源有序分配:为资源分配一个全局顺序,所有线程都必须按照这个顺序来请求资源。

  2. 避免占有并等待:线程在请求资源前不持有任何资源,或者一次性请求所有需要的资源。

  3. 资源剥夺:允许系统在必要时从线程中剥夺资源。

  4. 超时机制:线程在请求资源时设置超时,如果在超时时间内没有获取到资源,就释放已持有的资源并重试。

  5. 死锁检测与恢复:系统定期检测死锁,并采取恢复措施,如回滚事务或重启线程。

#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;
}

在这个例子中,thread1thread2 都试图先锁定 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_guardstd::adopt_lock 参数告诉 std::lock_guard,互斥锁已经被锁定,并且 std::lock_guard 应该在析构时解锁它们。

注意事项

  1. 避免死锁:使用 std::lock 可以减少死锁的风险,因为它确保所有互斥锁同时被锁定或解锁。

  2. 异常安全std::lockstd::lock_guard 结合使用,可以确保即使在抛出异常的情况下,互斥锁也能被正确解锁。

  3. 性能考虑:虽然 std::lock 提供了方便的一次性锁定多个互斥锁的能力,但它可能比单独锁定每个互斥锁有更高的性能开销。在性能敏感的应用中,应谨慎使用。

  4. 可扩展性std::lock 可以处理任意数量的互斥锁,这使得它在需要锁定多个资源时非常灵活。

通过使用 std::lockstd::lock_guard,你可以编写更安全、更易于维护的并发代码。(谨慎使用)

智能锁(unique_lock)

std::unique_lock 是 C++11 引入的一个类模板,它提供了一种灵活的方式来管理互斥量(mutex)。与 std::lock_guard 相比,std::unique_lock 更加灵活,允许在不同的作用域和不同的锁定策略之间进行选择。以下是 std::unique_lock 的一些主要特点和用法:

特点

  1. 灵活性std::unique_lock 可以在构造时选择是否锁定互斥量,支持手动锁定和解锁,允许条件变量的等待,以及在等待条件变量时自动解锁和重新锁定。

  2. 避免死锁:由于 std::unique_lock 的 RAII 特性,确保在作用域结束时自动解锁,降低了因忘记解锁而引起死锁的风险。

  3. 可以延迟锁定:你可以在构造 unique_lock 时不锁定互斥量,并在后面需要时再手动锁定。

  4. 可移动std::unique_lock 是可移动的,但不可复制,这使得它可以在线程间安全地转移锁的所有权。

adopt_lock
  • 延迟锁定:可以在构造时选择不锁定互斥量,并在需要时手动锁定。

std::adopt_lock 是 C+++11 引入的一个标签类型,用于指示 std::unique_lockstd::lock_guard 在构造时不要尝试去锁定传入的互斥锁(mutex),因为互斥锁已经被当前线程锁定。这个标签主要用于在已经手动锁定互斥锁的情况下,将互斥锁的管理权转移给 std::unique_lockstd::lock_guard 对象,以利用它们的异常安全保证(RAII)。

使用 std::adopt_lock 时,你必须确保在构造 std::unique_lockstd::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_lockstd::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_lockstd::unique_lock 的构造函数的一个可选参数,用于指示 std::unique_lock 的实例在初始化时不立即锁定互斥锁,而是将其保持在未锁定状态。这使得开发者可以在之后的某个时刻显式地调用 lock 方法来获取锁。

使用 std::defer_lock 的优点包括:

  1. 延迟锁定:允许开发者在构造 std::unique_lock 实例后,根据需要决定何时锁定互斥锁。
  2. 避免死锁:通过延迟锁定,可以减少在获取多个锁时发生死锁的风险。
  3. 灵活性:提供了在尝试锁定之前执行其他操作的能力,例如检查条件或执行初始化。
#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_lockrelease 成员函数用于释放与互斥锁(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;
}




网站公告

今日签到

点亮在社区的每一天
去签到

热门文章