Qt/C++中的多线程同步机制详解

同步操作就像排队一样,你做一件事,得等到它完成才能继续做下一件事。比如你在电商网站购物,点了“购买”按钮后,系统会马上查库存,这时候你会看到一个加载的提示,必须等到库存查完才能继续支付。如果库存不够,系统会报错,你就不能继续操作了。
异步操作则不同,你做一件事,不需要一直等着,可以继续做其他事情。比如你付完钱下单后,系统会给你发订单确认邮件。这个过程是异步的,你不用等着邮件发完,可以继续浏览网站或者做其他事情。邮件在后台自己发,就算发邮件出问题,也不影响你购物或者订单的有效性。异步
在多线程编程中,常常需要确保多个线程对共享资源的访问不会产生数据竞争。为此,我们使用同步机制来保证线程安全。
在Qt/C++中,常见的同步机制包括互斥锁(QMutex、std::mutex)、信号量(QSemaphore)、读写锁(QReadWriteLock)、原子操作(QAtomicInt 等)条件变量(QWaitCondition、std::condition_variable)将详细介绍这些机制,配合代码示例和注释,帮助你理解这些工具在多线程中的应用。

1、互斥锁(QMutex / std::mutex)

互斥锁是一种常见的同步工具,用于防止多个线程同时进入临界区(共享资源的代码段)。
在任何时刻,只有一个线程可以持有互斥锁并进入临界区,其他线程必须等待锁被释放后才能继续执行。
示例代码及注释(QMutex)

#include 
#include 
#include 
QMutex mutex;
int sharedResource = 0;
class Worker : public QThread 
{
public:    
	void run() override {        
			mutex.lock();        
			std::cout << "Thread " << QThread::currentThreadId() << " is entering the critical section." << std::endl;        
			sharedResource++;        
			std::cout << "Shared Resource: " << sharedResource << std::endl;        
			mutex.unlock();    
	}
};
int main()
{    
	Worker worker1, worker2;    
	worker1.start();    
	worker2.start();    
	worker1.wait();    
	worker2.wait();    
	return 0;
}

互斥锁的锁定与解锁:使用 mutex.lock() 锁定互斥锁,保证同一时刻只有一个线程能够进入修改 sharedResource 的临界区。执行完临界区的代码后,必须调用 mutex.unlock() 解锁。
线程竞争:两个线程 worker1 和 worker2 竞争访问共享资源 sharedResource,通过互斥锁保证安全。

2、信号量(QSemaphore)

信号量是一种用于控制多个线程访问有限资源的同步机制。它允许多个线程进入临界区,但总数受到信号量的限制。可以看作是一个资源计数器,线程需要“获取”信号量才能继续执行,并在完成后“释放”信号量。
示例代码及注释(QSemaphore)

#include 
#include 
#include 

QSemaphore semaphore(3); // 允许同时有3个线程进入

class Worker : public QThread 
{
public:    
	void run() override {        
		semaphore.acquire();  // 获取信号量        
		std::cout << "Thread " << QThread::currentThreadId() << " is entering." << std::endl;        			  
		QThread::sleep(1);    // 模拟工作        
		std::cout << "Thread " << QThread::currentThreadId() << " is leaving." << std::endl;        		
		semaphore.release();  // 释放信号量    
		}
};
int main() 
{    
	Worker worker1, worker2, worker3, worker4;    
	worker1.start();    
	worker2.start();    
	worker3.start();    
	worker4.start();    
	worker1.wait();    
	worker2.wait();    
	worker3.wait();    
	worker4.wait();    
	return 0;
}

信号量的获取与释放:semaphore.acquire() 减少可用资源计数器,semaphore.release() 增加资源计数器。只有计数器大于零时,线程才能继续执行。并发限制:最多有3个线程可以同时进入临界区,超过的线程必须等待其他线程释放资源。

3、读写锁(QReadWriteLock)

读写锁允许多个线程同时读取共享资源,但写操作是互斥的,即在写入时其他线程不能进行读或写操作。这适合多读少写的场景,提升了性能。
示例代码及注释(QReadWriteLock)

#include 
#include 
#include 
QReadWriteLock lock;
int sharedResource = 0;
class Reader : public QThread {
public:    
	void run() override {        
	lock.lockForRead();  // 获取读锁        
	std::cout << "Reader thread " << QThread::currentThreadId() << " reading: " << sharedResource << std::endl;        
	lock.unlock();       // 释放读锁    
	}
};
	
class Writer : public QThread {
public:    
	void run() override {        
		lock.lockForWrite();  // 获取写锁        
		sharedResource++;       
		std::cout << "Writer thread " << QThread::currentThreadId() << " writing: " << sharedResource << std::endl;       
		 lock.unlock();        // 释放写锁    
	 }
};
int main() 
{    
	Reader reader1, reader2;    
	Writer writer1;    
	reader1.start();    
	reader2.start();    
	writer1.start();    
	reader1.wait();    
	reader2.wait();    
	writer1.wait();    
	return 0;
}

读写锁:lock.lockForRead() 允许多个线程同时读取,lock.lockForWrite() 使得写入操作期间其他读写线程必须等待。读写互斥:写线程 writer1 阻塞其他读线程,直到写操作完成后其他线程才能继续读。

4、原子操作(QAtomicInt / std::atomic)

原子操作是最轻量的同步机制之一,它通过硬件保证增减操作的原子性,不需要加锁。适合计数器等简单的共享数据操作。
示例代码及注释(QAtomicInt)

#include 
#include 
#include 
QAtomicInt atomicCounter = 0;
class Worker : public QThread {
public:    
	void run() override {        
		for (int i = 0; i < 1000; ++i) 
		{            
			atomicCounter.ref();  // 原子增操作        
		}    
	}
};
int main() 
{    
	Worker worker1, worker2;    
	worker1.start();    
	worker2.start();    
	worker1.wait();    
	worker2.wait();    
	std::cout << "Final counter: " << atomicCounter.load() << std::endl;  // 输出最终计数值    
	return 0;
}

原子性操作:atomicCounter.ref() 是一个原子操作,无需加锁。硬件保证它在多个线程间的安全性。轻量同步:适合简单的增减计数操作,避免了使用锁带来的性能开销。
5、条件变量(QWaitCondition /std::condition_variable)
条件变量用于线程间的同步,它允许线程等待某个条件的满足。当条件满足时,其他线程会被唤醒并继续执行。示例代码及注释(QWaitCondition)

#include 
#include 
#include 
#include 
QMutex mutex;
QWaitCondition condition;
bool ready = false;
class Worker : public QThread 
{
public:    
	void run() override {        
		mutex.lock();        
		while (!ready) {            
			condition.wait(&mutex);  // 等待条件满足        
		}        
		std::cout << "Thread " << QThread::currentThreadId() << " is processing." << std::endl;        
		mutex.unlock();    
	}
};
int main() {    
	Worker worker1, worker2;    
	worker1.start();    
	worker2.start();        
	QThread::sleep(1);  // 模拟准备时间    
	mutex.lock();    
	ready = true;    
	condition.wakeAll();  // 唤醒所有等待线程    
	mutex.unlock();
  worker1.wait();    
  worker2.wait();    
  return 0;
}

条件变量等待:condition.wait(&mutex) 会使线程等待,并在等待期间释放互斥锁。一旦条件满足,线程会被唤醒并重新获取锁。条件变量唤醒:condition.wakeAll() 唤醒所有等待线程,线程会检查条件是否已满足并继续执行。

在多线程编程中,选择合适的同步机制非常重要。根据不同场景和需求,互斥锁、信号量、读写锁、原子操作、条件变量各有其适用范围:互斥锁 用于保护临界区,保证同一时刻只有一个线程访问共享资源。信号量 适合控制对有限资源的并发访问。读写锁 在多读少写的情况下能提供更好的性能。原子操作 是轻量级的同步方式,适合简单的计数操作。条件变量 则用于等待某个条件满足的线程间同步。

你可能感兴趣的:(QT,C/C++,qt,c++)