QSemaphore是互斥量的另外一种泛化表现形式,与读写锁定不同,信号量semaphore可以用于保护一定数量的资源。
一个典型的信号量应用程序是当两个线程间传递一定量的数据DataSize时,这两个线程会使用某一特定大小(BufferSize)的共享环形缓冲器buffer。
const int DataSize =10000;
const int BufferSize = 4096;
char buffer[BufferSize];
生产者线程向缓冲器中写数据,知道缓冲器的重点,然后从起点重新开始,覆盖已经存在的数据。消费者则会读取生成的数据。
生产者生产太快或者消费者线程读取太快,都会影响同步。
一个解决的方法是使用两个信号量:
QSmeaphore freeSpace(BufferSize);
QSmeaphore usedSpace(0);
freeSpace信号量控制生产者线程写入数据的那部分缓冲器,usedSpace控制消费者线程读取数据的那部分缓冲器区域。这两个区域相互补充。
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "ACGT"[uint(std::rand()) % 4];
usedSpace.release();
}
}
在生产者线程中,每次反复写入都是从获取一个“自由”字节开始,若缓冲器中填满消费者线程还没有读取的数据,则对acquire()的调用就会被阻塞,知道消费者开始消费,一旦获取,变开始写入随机数据,并将这个字节释放为“用过”的字节,以便让消费者读取到
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
std::cerr << buffer[i % BufferSize];
freeSpace.release();
}
std::cerr << std::endl;
}
消费者线程则从用过的字节开始,一旦获取了,就是放为自由字节,这样生产者线程就可以复写它。
int main()
{
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
为了跟踪过程,可以禁用写入输出数据,让生产者线程在每次产生一个字节时候输出P,让消费者线程每次读取一个字节时候输出C,
最后可能的结果是PCPCPCPC或者是PPPPCCCC,或者是其他,第一种说明线程的执行速度是一样的。
信号为系统特定的线程调用程序提供了很大的自由。
重点:与主线程的通信
Qt应用程序开始执行时候,只有主线程在运行,主线程唯一允许创建QApplication或者QCoreApplication对象,并且对创建的对象调用exec()线程,调用exec()线程后等待一个时间或者处理一个事件。
在次线程与主线程之间通信的一个解决方案是在线程之间使用信号和槽连接。
ImageWindow::ImageWindow()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Dark);
imageLabel->setAutoFillBackground(true);
imageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
setCentralWidget(imageLabel);
createActions();
createMenus();
statusBar()->showMessage(tr("Ready"), 2000);
connect(&thread, SIGNAL(transactionStarted(const QString &)),
statusBar(), SLOT(showMessage(const QString &)));
connect(&thread, SIGNAL(allTransactionsDone()),
this, SLOT(allTransactionsDone()));
setCurrentFile("");
}
void ImageWindow::flipHorizontally()
{
addTransaction(new FlipTransaction(Qt::Horizontal));
}
void ImageWindow::flipVertically()
{
addTransaction(new FlipTransaction(Qt::Vertical));
}
void ImageWindow::resizeImage()
{
QDialog dialog;
Ui::ResizeDialog ui;
ui.setupUi(&dialog);
ui.widthSpinBox->setValue(imageLabel->pixmap()->width());
ui.heightSpinBox->setValue(imageLabel->pixmap()->height());
if (dialog.exec()) {
QSize newSize(ui.widthSpinBox->value(),
ui.heightSpinBox->value());
addTransaction(new ResizeTransaction(newSize));
}
}
void ImageWindow::convertTo32Bit()
{
addTransaction(new ConvertDepthTransaction(32));
}
void ImageWindow::convertTo8Bit()
{
addTransaction(new ConvertDepthTransaction(8));
}
void ImageWindow::convertTo1Bit()
{
addTransaction(new ConvertDepthTransaction(1));
}
以上五个函数各自创建一个事物并且用私有函数addTransaction()注册它,
void ImageWindow::addTransaction(Transaction *transact)
{
thread.addTransaction(transact);
openAction->setEnabled(false);
saveAction->setEnabled(false);
saveAsAction->setEnabled(false);
}
addTransaction()函数会向次线程的食物队列中添加一个事物,并且在处理这些事物时候禁止open ,save ,save as 等操作。
void ImageWindow::allTransactionsDone()
{
openAction->setEnabled(true);
saveAction->setEnabled(true);
saveAsAction->setEnabled(true);
imageLabel->setPixmap(QPixmap::fromImage(thread.image()));
setWindowModified(true);
statusBar()->showMessage(tr("Ready"), 2000);
}
当transactionThread的事物队列变为空的时候,就是调用allTransactionDone()槽。
接下来是TransactionThread类,run()函数在自己的线程内执行,而构造和析构函数在主线程中执行
class TransactionThread : public QThread
{
Q_OBJECT
public:
TransactionThread();
~TransactionThread();
void addTransaction(Transaction *transact);
void setImage(const QImage &image);
QImage image();
signals:
void transactionStarted(const QString &message);
void allTransactionsDone();
protected:
void run();
private:
QImage currentImage; //保存事物应用的图片
QQueue<Transaction *> transactions; //待处理事务队列
QWaitCondition transactionAdded; //一个等待条件,当有新的事物添加到队列中时,用于触发线程
QMutex mutex; //防止currentImage和transaction成员变量并发访问
};
TranscationThread类维护着一个事物队列,并且在后台一个个处理并执行
setImage和image()函数可以让主线程设立一个图片,在该图片上可以处理事务,并且可以在所有事物处理完毕的时候找回最终的结果图片。
void TransactionThread::setImage(const QImage &image)
{
QMutexLocker locker(&mutex);
currentImage = image;
}
QImage TransactionThread::image()
{
QMutexLocker locker(&mutex);
return currentImage;
}
void TransactionThread::run()
{
Transaction *transact = 0;
QImage oldImage;
forever {
{
QMutexLocker locker(&mutex);
if (transactions.isEmpty())
transactionAdded.wait(&mutex);
transact = transactions.dequeue();
if (transact == EndTransaction)
break;
oldImage = currentImage;
}
emit transactionStarted(transact->message());
QImage newImage = transact->apply(oldImage);
delete transact;
{
QMutexLocker locker(&mutex);
currentImage = newImage;
if (transactions.isEmpty())
emit allTransactionsDone();
}
}
}
函数run()会遍历事物队列,并且可以通过对它们调用apply()函数依次执行每一个事物,知道读到EndTransaction标记为止,如果事物队列为空,则线程在事物添加条件下面等待。
class Transaction
{
public:
virtual ~Transaction() { }
virtual QImage apply(const QImage &image) = 0;
virtual QString message() = 0;
};
Transaction类是一个用户可以对图片进行相关操作的抽象类基类,需要通过一个Transaction指针来删除Transaction子类的实例,因此虚析构函数是必要的。
Transaction类有三个具体子类
class FlipTransaction : public Transaction
{
public:
FlipTransaction(Qt::Orientation orientation);
QImage apply(const QImage &image);
QString message();
private:
Qt::Orientation orientation;
};
class ResizeTransaction : public Transaction
{
public:
ResizeTransaction(const QSize &size);
QImage apply(const QImage &image);
QString message();
private:
QSize size;
};
class ConvertDepthTransaction : public Transaction
{
public:
ConvertDepthTransaction(int depth);
QImage apply(const QImage &image);
QString message();
private:
int depth;
};
FlipTransaction::FlipTransaction(Qt::Orientation orientation)
{
this->orientation = orientation;
}
QImage FlipTransaction::apply(const QImage &image)
{
return image.mirrored(orientation == Qt::Horizontal,
orientation == Qt::Vertical);
}
QString FlipTransaction::message()
{
if (orientation == Qt::Horizontal) {
return QObject::tr("Flipping image horizontally...");
} else {
return QObject::tr("Flipping image vertically...");
}
}
上面给出FlipTransaction里面的函数的实践。