使用QThread实现多线程
#include "qlib.h"
class PrintThread : public QThread {
private:
//通过bool来控制运行条件
bool running = true;
public:
PrintThread() {}
//在析构函数中自动结束线程
//很重要,可以在主线程结束时,保证子线程正常结束
~PrintThread() {
running = false;
this->requestInterruption();
this->wait();
}
protected:
//重写run方法,指定线程工作
void run() {
while (running) {
wcout << L"线程1正在运行" << endl;
msleep(500);
}
}
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
//创建并开始线程
PrintThread t1;
t1.start();
return app.exec();
}
使用lambda创建QThread
QThread有一个很明显的缺点就是,工作任务都写在run方法里面,如果工作任务不同,就要重新定义一个QThread类
我们可以将run方法抽取出来,通过函数指针传入一个方法来指定工作任务,再通过lambda来简化定义方法的代码
#include "qlib.h"
class PrintThread : public QThread {
private:
bool running = true;
//通过函数指针来指定run方法的工作
void (*pFunc)(PrintThread* t) = nullptr;
public:
PrintThread(void (*pFunc)(PrintThread* t)) : QThread() {
this->pFunc = pFunc;
}
~PrintThread() {
running = false;
this->requestInterruption();
this->wait();
pFunc = nullptr;
}
bool isRunning() {
return running;
}
protected:
void run() {
//调用函数指针来处理工作
while (running) pFunc(this);
}
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
//通过lambda来创建线程,这样就可以让不同线程处理不同的工作
PrintThread* t1 = new PrintThread([](PrintThread* t) -> void {
while (t->isRunning()) {
wcout << L"线程1正在运行" << endl;
t->msleep(500);
}
});
t1->start();
//再创建一个线程
PrintThread* t2 = new PrintThread([](PrintThread* t) -> void {
while (t->isRunning()) {
wcout << L"线程2正在运行" << endl;
t->msleep(500);
}
});
t2->start();
return app.exec();
}
使用QtConcurrent实现多线程
#include "qlib.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
//开启一个线程执行方法
//异步执行,执行完后通过QFuture.value可以获得结果
QFuture<int> future1 = QtConcurrent::run(
[](int a, int b) -> int {
for (int i = 0; i < 100000; i++) wcout << L"线程1正在运行" << endl;
return 1;
},
1, 2);
//再开启一个线程执行方法
QFuture<int> future2 = QtConcurrent::run([]() -> int {
for (int i = 0; i < 100000; i++) wcout << L"线程2正在运行" << endl;
return 1;
});
//执行带参数的方法
auto pFunc = [](int count) -> int {
for (int i = 0; i < count; i++) wcout << L"线程3正在运行" << endl;
return 1;
};
QFuture<int> future3 = QtConcurrent::run(pFunc, 100000);
//等待执行完毕,获取结果
//这个操作会阻塞主线程
//如果不想阻塞主线程,可以不使用QFuture,而是在子线程中通过信号槽来通知主线程
future1.waitForFinished();
future2.waitForFinished();
future3.waitForFinished();
return app.exec();
}
在线程池中执行QtConcurrent
#include "qlib.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
//创建一个函数指针,指向匿名lambda方法
auto pFunc = [](string message) -> int {
for (int i = 0; i < 10; i++) {
cout << message << endl;
QThread::msleep(500);
}
return 1;
};
//创建一个线程池
//如果线程池中有足够线程,则所有任务并发执行
//如果线程池中只有一个线程,则所有任务轮流执行
//单例线程池经过适当封装,就可以作为一个任务队列使用
QThreadPool threadPool;
threadPool.setMaxThreadCount(1);
//向线程池中添加任务,并立刻执行
string param1 = "Thread-1";
string param2 = "Thread-2";
string param3 = "Thread-3";
QFuture<int> future1 = QtConcurrent::run(&threadPool, pFunc, param1);
QFuture<int> future2 = QtConcurrent::run(&threadPool, pFunc, param2);
QFuture<int> future3 = QtConcurrent::run(&threadPool, pFunc, param3);
return app.exec();
}
利用C++原生代码实现多线程
#include "qlib.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
//创建一个函数指针,指向匿名lambda方法
auto pFunc = [](string message) {
for (int i = 0; i < 100; i++) {
cout << message << endl;
QThread::msleep(500);
}
};
//创建线程
std::thread t1(pFunc, "Thread-A is Running");
std::thread t2(pFunc, "Thread-B is Running");
std::thread t3(pFunc, "Thread-C is Running");
return app.exec();
}
C++原生代码中止线程
#include "qlib.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
auto pFunc = [](bool* running) {
while (*running) {
cout << "Thread-1 is Running" << endl;
QThread::msleep(1000);
}
};
//创建一个线程打印
bool* running = new bool(true);
std::thread t1(pFunc, running);
//创建一个定时线程,10秒后停止打印线程
std::thread t2([running] {
QThread::msleep(10000);
*running = false;
cout << "Stop Thread-1" << endl;
});
return app.exec();
}
通过QMutex控制线程同步
#include "qlib.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QMainWindow w;
w.show();
//设置字符编码
setlocale(LC_ALL, ".ACP");
QMutex* mutex = new QMutex();
QtConcurrent::run([mutex]() {
mutex->lock();
wcout << L"线程1获得同步锁" << endl;
QThread::msleep(5000);
wcout << L"线程1释放同步锁" << endl;
mutex->unlock();
});
QtConcurrent::run([mutex]() {
QThread::msleep(1000);
wcout << L"线程2等待同步锁" << endl;
mutex->lock();
wcout << L"线程2获得同步锁" << endl;
mutex->unlock();
});
return app.exec();
}
简化的QMutex:QMutexLocker
QMutexLocker是对QMutex的封装简化,它持有一个QMutex的引用
它会在创建时通过构造函数自动调用QMutex.lock方法,在作用域结束时,通过析构函数自动调用QMutex.unlock方法
QMutex mutex;
QtConcurrent::run([&mutex]() {
QMutexLocker locker(&mutex);
wcout << L"线程1获得同步锁" << endl;
QThread::msleep(5000);
wcout << L"线程1释放同步锁" << endl;
});
QtConcurrent::run([&mutex]() {
QThread::msleep(1000);
wcout << L"线程2等待同步锁" << endl;
QMutexLocker locker(&mutex);
wcout << L"线程2获得同步锁" << endl;
});
QReadWriteLock
QReadWriteLock中的Read/Write,并不代表实际操作是读还是写,仅仅为了方便使用,是提供了两种不同类型的锁
一个线程通过lockForWrite获取了锁,其它线程调用lockForWrite就得等待,调用lockForRead则无需等待
一个线程通过lockForRead获取了锁,其它线程调用lockForRead就得等待,调用lockForWrite则无需等待
至于执行了lockForWrite之后,是不是真的进行了写操作,对Lock是毫无影响的
当然,我们尽量让实际操作和QReadWriteLock的逻辑意义一致,不要去破坏接口本身的设计意图
QReadWriteLock lock;
QtConcurrent::run([&lock]() {
wcout << L"线程1获得同步锁" << endl;
lock.lockForWrite();
wcout << L"线程1写入数据" << endl;
QThread::msleep(5000);
wcout << L"线程1释放同步锁" << endl;
lock.unlock();
});
QtConcurrent::run([&lock]() {
QThread::msleep(1000);
wcout << L"线程2等待同步锁" << endl;
lock.lockForWrite();
wcout << L"线程2写入数据" << endl;
wcout << L"线程2释放同步锁" << endl;
lock.unlock();
});
简化的QReadWriteLock:QReadLock,QWriteLock
QReadLock,QWriteLock和QMutexLocker的原理用法都是一致的
即持有一个QReadWriteLock引用,自动加锁解锁
QReadWriteLock lock;
QtConcurrent::run([&lock]() {
wcout << L"线程1获得同步锁" << endl;
QWriteLocker locker(&lock);
wcout << L"线程1写入数据" << endl;
QThread::msleep(5000);
wcout << L"线程1释放同步锁" << endl;
});
QtConcurrent::run([&lock]() {
QThread::msleep(1000);
wcout << L"线程2等待同步锁" << endl;
QWriteLocker locker(&lock);
wcout << L"线程2写入数据" << endl;
wcout << L"线程2释放同步锁" << endl;
});
QSemaphore
QSemaphore在QMutex的基础上,增加了一个并发量功能,可允许若干个线程同时访问
当QSemaphore并发量达到指定上限,信号量用完时,申请信号量的线程就需要等待
QSemaphore semaphore(3);
QtConcurrent::run([&semaphore]() {
semaphore.acquire(3);
wcout << L"线程1获得三个信号量" << endl;
QThread::msleep(5000);
wcout << L"线程1释放一个信号量" << endl;
semaphore.release(1);
});
QtConcurrent::run([&semaphore]() {
QThread::msleep(1000);
wcout << L"线程2请求一个信号量" << endl;
semaphore.acquire(1);
wcout << L"线程2获得一个信号量" << endl;
wcout << L"线程2请求一个信号量" << endl;
semaphore.acquire(1); //由于线程1只释放了一个信号量,这里会无限等待
wcout << L"线程2获得一个信号量" << endl;
});
QWaitCondition
QWaitCondition可以允许线程在条件不符合时,进入等待状态,等到条件成立时,再继续执行
QWaitCondition需要配合QMutex使用,以保证在多线程情景下,只有一个线程会修改条件值,从而保证条件值的准确性
//这里以生产者消费者案例,来模拟QWaitCondition的应用场景
//生产者生产物品加入队列,消费者从队列中取出物品消费
//仓库已满,则生产者不再生产,队列已空,则消费者不再消费
const long produceInterval = 500; //生产间隔
const long consumeInterval = 1000; //消费间隔
const int storeSize = 1; //仓库容量,达到仓库上限后,不再生产
QQueue<string> productQueue; //产品队列,队列为空时,不再消费
QMutex mutex; //用来保证队列在并发时的同步性
QWaitCondition storeIsFull; //根据仓库是否已满,来等待或通知
QWaitCondition storeIsEmpty; //根据仓库是否已空,来等待或通知
//生产者线程
QtConcurrent::run([&productQueue, &mutex, &storeIsFull, &storeIsEmpty]() {
while (true) {
mutex.lock();
//如果仓库已满,则不再生产,进入等待状态,并交出productQueue的使用权
if (productQueue.size() == storeSize) storeIsFull.wait(&mutex);
//收到其它线程的通知,仓库已经有空位,继续生产,重新获得productQueue的使用权
string productName = QUuid::createUuid().toString().toStdString();
productQueue.enqueue(productName);
storeIsEmpty.wakeOne();
cout << "Produce Product:" << productName << endl;
mutex.unlock();
QThread::msleep(produceInterval);
}
});
//消费者线程
QtConcurrent::run([&productQueue, &mutex, &storeIsFull, &storeIsEmpty]() {
while (true) {
mutex.lock();
//如果仓库已空,则不再消费,并交出productQueue的使用权
if (productQueue.isEmpty()) storeIsEmpty.wait(&mutex);
//收到其它线程的通知,仓库已经有货物,继续消费,重新获得productQueue的使用权
string productName = productQueue.dequeue();
cout << "Consume Product:" << productName << endl;
storeIsFull.wakeOne();
mutex.unlock();
QThread::msleep(consumeInterval);
}
});
QWaitCondition和QMutex的区别
其实通过以上讲解,大家已经能够自己总结出两者的区别,为表郑重,特地总结下
- QMutex是占有式的,一个线程lock后,其它线程都无法lock
- QWaitCondition是通知式的,条件不符合,当前线程就进入wait状态,其它线程可以通过wake打破当前线程的wait状态
- 一个用来竞争资源使用权,一个用来通知其它线程条件变更,是两种不同的功能
- QWaitCondition需要配合QMutex,因为如果没有QMutex去保护条件的话,wake之后,条件值就可能被其它线程修改
C++标准库线程同步接口
C++标准库也提供了mutex,lock,condition等功能
不止C++,Java等语言基本都有这些接口,而且使用方法基本雷同
因此不再详细阐述,需要使用标准库的可以自己了解下
std::mutex mutex;
std::thread t1([&mutex]() {
mutex.lock();
cout << "Thread-1" << endl;
cout << "Thread-1" << endl;
cout << "Thread-1" << endl;
});
std::thread t2([&mutex]() {
mutex.lock();
cout << "Thread-2" << endl;
cout << "Thread-2" << endl;
cout << "Thread-2" << endl;
});
//t1和t2只有一个会打印成功,因为mutex没有执行unlock操作