今天团建, 但是文章也要写. 酒要喝好, 文要写美, 方为我辈程序员的全才之路. 嘎嘎
之前一直在看 POSIX 的多线程编程, 上个周末结合自己的理解, 写了一个基于 Qt 的用条件变量同步线程的例子. 故此来和大家一起分享, 希望和大家一起交流.
提到线程, 如果在 UI 编程中, 总会和一些耗时操作联系在一起. Qt 中处理耗时操作通常有两种方式, 一种是将耗时操作放在线程中; 另一种则是使用 QApplication::processEvents(), 防止阻塞 UI. 从更加通用的角度来讲, 我是更倾向于线程的, 但对于很多初学者来讲, 线程还是有一定难度的. 比如说需要对线程间共享的数据提供保护, 使用互斥量同步, 使用条件变量, 使用读写锁同步等; 各种同步方式用在什么情况下, 开始编程时多线程使用的并不多, 无法切身体会到这些问题, 后来程序写的多了一点儿, 慢慢接触到一些多线程的东西, 并且自己也可以学习了相关知识, 并用到实际程序中. 好了, 下面以一个实际的例子为背景, 来说明 Linux POSIX 多线程的一些特性.
程序环境: Ubuntu 14.04,Qt 5.5.1,Posix 多线程(C 的用法)
这里简单说下我为什么用 Linux C 的多线程, 因为 Qt 的多编程对于一些线程的终止时含糊不清楚的, 并且一个线程被终止后的资源是无法被清理的, 所以我选择是相对底层的一些用法, 以后有机会我还会添加线程取消和线程退出的操作.
我自己设定的场景是这样的, 在 UI 主线程中通过界面手动向一个线程间共享的队列中 push 数据, 而另外开启的一个线程则一直在 while 中 pop 数据, 这算是一个变种的生产者和消费者模式吧.
至于条件变量, 互斥量 (也就是互斥锁) 的初始化在这里不再详细说明, 只说明一些相对重要的地方.
1. UI 中向队列 push 数据(生产者生产数)
这是一个槽函数, 当在 lineEdit 中回车后, 则会触发该槽函数, 由于该队列是线程间的 共享数据, 所以使用了互斥锁进行保护, 即该槽操作数据的过程中如果有其他线程想要操作数据, 则其他线程则会被阻塞, 即访问一个已经被加锁的互斥量的线程会被阻塞.
- void Widget::on_le_writeNum_returnPressed()
- {
- int status;
- status = pthread_mutex_lock (&mp_processThread->m_structCondition.mutex);
- if (status != 0)
- err_abort (status, "Lock mutex");
- QString num = ui->le_writeNum->text();
- mp_processThread->queuePushData(num.toInt());
- status = pthread_cond_signal (&mp_processThread->m_structCondition.cond);
- // status = pthread_cond_broadcast( &mp_processThread->m_structCondition.cond);
- if (status != 0)
- err_abort (status, "Signal condition");
- status = pthread_mutex_unlock (&mp_processThread->m_structCondition.mutex);
- if (status != 0)
- err_abort (status, "Unlock mutex");
- }
2. 消费者线程 pop 数据
该线程使用的是 Qt 的 moveToThread 方法创建的线程, 这里注意的是, 整个类都运行在新的线程中. 该槽函数随着线程的启动信号 (start()) 发射后而一直进行 while 循环. 首先对互斥量上锁, 之后判断谓词状态, 如果队列为空, 则等待条件变量. 等待条件变量时 pthread_cond_wait()会自动释放互斥锁, 这样其他线程才能够操作共享数据. 从条件变量等待中醒来后, 会再次获得互斥锁, 以操作共享数据. 共享数据被操作完成后, 再次释放互斥锁. 这是我们使用条件变量等待的一个操作流程, 如果我们不使用条件变量等待会是怎样的呢?
- void ProcessThread::slot_processData()
- {
- int status;
- while(!mb_stopThread)
- {
- status = pthread_mutex_lock (&m_structCondition.mutex);
- if (status != 0)
- err_abort (status, "Lock mutex");
- while(m_queue.empty()) //if queue is empty, wait contion
- {
- // 使用条件变量等待
- status = pthread_cond_wait(&m_structCondition.cond,
- &m_structCondition.mutex);
- // qDebug() << "pthread_cond_wait is block func!";
- if (status != 0)
- {
- err_abort (status, "Wait on cond faild");
- }
- }
- while(!m_queue.empty())
- {
- qDebug() << "queue mem is" << m_queue.back();
- m_queue.pop();
- }
- status = pthread_mutex_unlock (&m_structCondition.mutex);
- if (status != 0)
- err_abort (status, "Unlock mutex");
- }
- }
3. 不使用条件变量等待
1不使用条件变量等待
如果不使用条件变量等待, 则消费者线程在很大一部时间内几乎都是在执行 while(1)无限循环, 这是很占用 CPU 资源的, 在 Ubuntu 下, 使用 htop 查看的效果 如下:
屏蔽 status = pthread_cond_wait(&m_structCondition.cond,
&m_structCondition.mutex);
我们看到, 此时 CPU 是满负荷在运行的, 这当然不是一个程序所应有的正常状态.
2使用条件变量的结果
此时我们看到 CPU 的占用率是很低的, 这也是为什么使用条件变量的原因之一, 让不满足的条件的线程挂起, 而不是在浪费 CPU 资源. 条件变量是 允许使用队列的线程之间交换队列状态信息的机制. 那么当我们还没有掌握线程条件变量的用法时, 又遇到这种情况时, 该怎么做呢? 简单, 加个 5ms 的延时即可, 5ms 对我们来讲时间极短极短, 但对计算机来讲, 已经挺长时间了.
最后, 当我们关掉 UI 窗口时, 会有这样一句消息:
QThread: Destroyed while thread is still running
线程正在运行时就被破坏了, 这个我们接下来会说, 那就是如何退出线程, 终止线程以及取消线程等操作了.
欢迎大家一起交流!
来源: https://www.cnblogs.com/d-h-/p/11286171.html