Qt 一次性把多线程实现同步方式说清楚
概念(为何需要同步)
1.多个线程同时读写共享资源(全局/静态内存变量、堆内存动态分配对象、文件/IO/网络句柄、外设资源等),执行顺序不确定,导致数据错乱、崩溃;
2.同步主要是为了保证共享资源互斥访问、线程等待/唤醒、有序执行;
3.测试(没添加同步)
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> int g_count_mutex = 0; void mutexTask() { qDebug() << "子线程ID:" << QThread::currentThreadId(); for (int i = 0; i < 10000; ++i) { g_count_mutex++; QThread::msleep(1); } } void demo_QMutex() { g_count_mutex = 0; QFuture<void> t1 = QtConcurrent::run(mutexTask); QFuture<void> t2 = QtConcurrent::run(mutexTask); t1.waitForFinished(); t2.waitForFinished(); qDebug() << "QMutex 最终计数:" << g_count_mutex; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QMutex(); return 0; //return a.exec(); }一、方式1
1.QMutex基础互斥锁:
1)保证同一时间仅一个线程进入临界区,保护共享变量读写;
2)API:
--lock(): 加锁,已被占用则阻塞线程,直到释放;
--tryLock(): 非阻塞尝试加锁,获取成功返回true,失败立即返回false;
--tryLock(int timeout): 带超时,超时内拿不到锁返回false;
--unlock(): 释放锁,必须与lock成对调用。
2.测试
main.cpp
//全局共享变量 + 互斥锁 QMutex g_mutex; int g_count_mutex = 0; void mutexTask() { qDebug() << "子线程ID:" << QThread::currentThreadId(); for (int i = 0; i < 10000; ++i) { g_mutex.lock(); g_count_mutex++; g_mutex.unlock(); } } void demo_QMutex() { g_count_mutex = 0; QFuture<void> t1 = QtConcurrent::run(mutexTask); QFuture<void> t2 = QtConcurrent::run(mutexTask); t1.waitForFinished(); t2.waitForFinished(); qDebug() << "QMutex 最终计数:" << g_count_mutex; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QMutex(); return 0; //return a.exec(); }二、方式2
1.QMutexLocker(RAII 自动锁):
1)栈对象构造时自动lock,离开作用域(return/异常/花括号结束)自动unlock,无需手动释放;
2.测试
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> //全局共享变量 + 互斥锁 QMutex g_mutex; int g_count_mutex = 0; void mutexLockerTask() { qDebug() << "子线程ID:" << QThread::currentThreadId(); for (int i = 0; i < 10000; ++i) { QMutexLocker locker(&g_mutex); //RAII自动加锁/解锁 g_count_mutex++; //QThread::msleep(1); } } void demo_QMutexLocker() { g_count_mutex = 0; QFuture<void> t1 = QtConcurrent::run(mutexLockerTask); QFuture<void> t2 = QtConcurrent::run(mutexLockerTask); t1.waitForFinished(); t2.waitForFinished(); qDebug() << "QMutexLocker 最终计数:" << g_count_mutex; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QMutexLocker(); return 0; //return a.exec(); }三、方式3
1.QReadWriteLock读写锁:
1)多个线程可同时获取读锁(读和读不冲突);
2)写锁独占: 有线程持有读锁/写锁时,写线程阻塞;持有写锁时,所有读写线程阻塞;
3)配套RAII工具: QReadLocker、QWriteLocker;
4)API:
--lockForRead(): 获取读锁;
--lockForWrite(): 获取写锁;
--tryLockForRead(int t = 0);
--tryLockForWrite(int t = 0);
--unlock();
2.测试
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> QReadWriteLock g_rwLock; int g_data_rw = 0; //读线程 void readTask() { for (int i = 0; i < 100; ++i) { QReadLocker lock(&g_rwLock); //读锁,允许多线程同时读 qDebug() << "读值:" << g_data_rw; QThread::msleep(5); } } //写线程 void writeTask() { for (int i = 0; i < 100; ++i) { QWriteLocker lock(&g_rwLock); //写锁,独占访问 g_data_rw++; qDebug() << "写入值:" << g_data_rw; QThread::msleep(10); } } void demo_QReadWriteLock() { g_data_rw = 0; QFuture<void> r1 = QtConcurrent::run(readTask); QFuture<void> r2 = QtConcurrent::run(readTask); QFuture<void> w1 = QtConcurrent::run(writeTask); r1.waitForFinished(); r2.waitForFinished(); w1.waitForFinished(); qDebug() << "QReadWriteLock 最终值:" << g_data_rw; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QReadWriteLock(); return 0; //return a.exec(); }四、方式4
1.QWaitCondition条件变量:
1)线程主动阻塞等待某个条件成立,其他线程修改条件后唤醒等待线程,实现线程间等待/通知,必须配合QMutex使用;
2)API:
--wait(QMutex*, unsigned long timeout): 释放锁,阻塞等待唤醒,超时自动返回;
--wakeOne(): 随机唤醒1个等待线程;
--wakeAll(): 唤醒全部等待线程。
2.测试
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> QMutex g_condMutex; QWaitCondition g_cond; int g_buffer = 0; bool g_hasData = false; //消费者 void consumerTask() { QMutexLocker lock(&g_condMutex); // while 循环防止虚假唤醒 while (!g_hasData) { g_cond.wait(&g_condMutex); } qDebug() << "消费数据:" << g_buffer; g_hasData = false; g_cond.wakeOne(); //通知生产者可以继续生产 } //生产者 void producerTask(int val) { QMutexLocker lock(&g_condMutex); while (g_hasData) { g_cond.wait(&g_condMutex); } g_buffer = val; g_hasData = true; qDebug() << "生产数据:" << g_buffer; g_cond.wakeOne(); } void demo_QWaitCondition() { g_hasData = false; QFuture<void> consumer = QtConcurrent::run(consumerTask); QThread::msleep(100); QFuture<void> producer = QtConcurrent::run(producerTask, 999); consumer.waitForFinished(); producer.waitForFinished(); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QWaitCondition(); return 0; //return a.exec(); }五、方式5
1.QSemaphore信号量:
1)内置计数器,控制有限资源的并发访问,本质是带计数的条件变量;
2)API:
--acquire(n): 申请n个资源,资源不足则阻塞;
--release(n): 释放n个资源,计数器 + n;
--available(): 返回当前剩余资源数量。
2.测试
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> QSemaphore g_sem(2); // 最多允许2个线程同时进入临界区 void semTask(int id) { g_sem.acquire(); // 占用1个资源,超出上限则阻塞 qDebug() << "任务" << id << "进入临界区"; QThread::msleep(1000); qDebug() << "任务" << id << "离开临界区"; g_sem.release(); // 释放资源 } void demo_QSemaphore() { QFuture<void> t1 = QtConcurrent::run(semTask, 1); QFuture<void> t2 = QtConcurrent::run(semTask, 2); QFuture<void> t3 = QtConcurrent::run(semTask, 3); QFuture<void> t4 = QtConcurrent::run(semTask, 4); t1.waitForFinished(); t2.waitForFinished(); t3.waitForFinished(); t4.waitForFinished(); qDebug() << "QSemaphore 全部任务结束"; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_QSemaphore(); return 0; //return a.exec(); }六、方式6
1.QBasicAtomic/std::atomic原子变量(无锁同步):
1)无锁,CPU 硬件指令保证单个变量原子读写,不需要加锁,性能最高;
2)仅支持基础类型:int、long、指针。
2.测试
main.cpp
#include <QCoreApplication> #include <QThread> #include <QMutex> #include <QMutexLocker> #include <QReadWriteLock> #include <QReadLocker> #include <QWriteLocker> #include <QWaitCondition> #include <QSemaphore> #include <QAtomicInt> #include <QAtomicPointer> #include <QAtomicInteger> #include <QtConcurrent/QtConcurrent> #include <QDebug> #include <atomic> //Qt原子变量 QBasicAtomicInt g_qtAtomic = Q_BASIC_ATOMIC_INITIALIZER(0); //C++标准原子变量 std::atomic<int> g_stdAtomic{0}; void atomicTask() { for (int i = 0; i < 5000; ++i) { g_qtAtomic.ref(); // Qt原子自增 g_stdAtomic.fetch_add(1);// C++原子自增 } } void demo_Atomic() { g_qtAtomic = 0; g_stdAtomic = 0; QFuture<void> t1 = QtConcurrent::run(atomicTask); QFuture<void> t2 = QtConcurrent::run(atomicTask); t1.waitForFinished(); t2.waitForFinished(); qDebug() << "QBasicAtomicInt 计数:" << g_qtAtomic.load(); qDebug() << "std::atomic 计数:" << g_stdAtomic.load(); } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); demo_Atomic(); return 0; //return a.exec(); }七、开发工具及Qt版本
开发工具:Qt Creator 4.10.2
Qt版本:Qt_5_12_6
