qt線程(轉(zhuǎn))----這篇很專業(yè)!
本文檔是自己所整理的一份文檔,部分是原創(chuàng),還轉(zhuǎn)貼了網(wǎng)上的一此資料(已經(jīng)標(biāo)明了),(難點是多線程的編寫),是有源代碼的,大家可以作為參考,用到的知識是視頻采集,壓縮解壓(xvid),實時傳輸(jrtp),基于qt庫所寫的,由于本人對qt下的多線程還不很了解,只做了單線程的(采集-->壓縮-->解壓-->發(fā)送-->接收--顯示),用timer來刷新視頻播放窗口,現(xiàn)在正在研究多線程(代碼還在整理中),以后再換成多線程(用qt4的多線程,因為qt4的線程繼承于QObject的,線程間可以使用signal-slot機制通信),建設(shè)先看看“l(fā)inux下的tv播放器.doc(網(wǎng)上的資料)”
一.把視頻顯示到界面的方法
(1)針對qt4的(視頻格式為rgb32)
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
QImage image(pBuffer,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
label->setPixmap(pixmap);
label->setFixedSize(pixmap.width(),pixmap.height());
(2)針對qt3的
1)格式為rgb32的
QImage *img;
unsigned char *bit=image;
setWFlags(getWFlags() | Qt::WRepaintNoErase);
img=new QImage((uchar *)bit,MAX_WIDTH, MAX_HEIGHT, 32,NULL,0,QImage::IgnoreEndian);
bitBlt(this, 0, 0, img);
2)格式為rgb24的
int x, y;
int i = 0;
#if 0
QLabel *label_time;
QTime time = QTime::currentTime();
label_time = new QLabel(time.toString(),this, "label_time" );
label_time->setGeometry( 5, 250, 160, 31 );
label_time->setAlignment( QLabel::AlignCenter );
#endif
v4l_grab_movie(&v4l_dev);
QString a;
QString d;
QImage img;
unsigned char *bit= v4l_dev.buffer;
QRgb *point;
int r, g, b;
QPainter paint;
//該步很重要,設(shè)置標(biāo)志
//讓QWidget在更新窗體時,不擦除原來的窗體
//這樣可以避免閃屏
setWFlags(getWFlags() | Qt::WRepaintNoErase);
if(img.create(MAX_WIDTH, MAX_HEIGHT, 32, 0, QImage::IgnoreEndian))
{
for(y=0; y<MAX_HEIGHT; y++)
{
for(x=0; x<MAX_WIDTH; x++)
{
r=(int)bit[i+2];
g=(int)bit[i+1];
b=(int)bit[i];
point= (QRgb *)(img).scanLine(y)+ x;
*point = qRgb(r,g,b);
i+=3;
}
}
}
paint.begin(this);
QDate date=QDate::currentDate();
d=date.toString();
QTime time = QTime::currentTime();
a=time.toString();
paint.drawImage(5, 5, (img));
paint.drawText(20,20,a,-1);
paint.drawText(20,30,d,-1);
paint.end();
二.qt的多線程問題(qt4與qt3有線程是很大不同的)
1)如果不用多線程,一般是通過QApplication的消息循環(huán)來處理的
2)QThread本身是繼承于QObject的,為線程間的signal-slot機制打下了基礎(chǔ)(Qt4),而qt3的線程不是繼承于QObject,不能在線程間使用signal-slot機制(如QObject::connect(Thread, SIGNAL(Log(QString)), this, SLOT(Logslots(QString)))不能應(yīng)用在qt3中,只能應(yīng)用在qt4中)
3)QObject本身和線程是沒關(guān)系的,提供signal-slot機制相關(guān)信息
三.事件和信號的區(qū)別(問題:哪什么時候用事件,什么時候用信號呢?是不是不同的線程間用事件,信號不能用在線程間?但現(xiàn)在的qt4可以在線程間發(fā)送信號)
仔細(xì)來看,事件與信號其實并無多大差別,從我們對其需求上來說,都只要能注冊事件或信號響應(yīng)函數(shù),在事件或信號產(chǎn)生時能夠被通知到即可。但有一項區(qū)別在于,事件處理函數(shù)的返回值是有意義的,我們要根據(jù)這個返回值來確定是否還要繼續(xù)事件的處理,比如在QT中,事件處理函數(shù)如果返回true,則這個事件處理已完成,QApplication會接著處理下一個事件,而如果返回false,那么事件分派函數(shù)會繼續(xù)向上尋找下一個可以處理該事件的注冊方法。信號處理函數(shù)的返回值對信號分派器來說是無意義的。
另外還有一個需要我們關(guān)注的問題是事件和信號處理時的優(yōu)先級問題。在QT中,事件因為都是與窗口相關(guān)的,所以事件回調(diào)時都是從當(dāng)前窗口開始,一級一級向上派發(fā),直到有一個窗口返回true,截斷了事件的處理為止。對于信號的處理則比較簡單,默認(rèn)是沒有順序的,如果需要明確的順序,可以在信號注冊時顯示地指明槽的位置。
在QT中,事件使用了一個事件隊列來維護,如果事件的處理中又產(chǎn)生了新的事件,那么新的事件會加入到隊列尾,直到當(dāng)前事件處理完畢后, QApplication再去隊列頭取下一個事件來處理。而信號的處理方式有些不同,信號處理是立即回調(diào)的,也就是一個信號產(chǎn)生后,他上面所注冊的所有槽都會立即被回調(diào)。這樣就會產(chǎn)生一個遞歸調(diào)用的問題,比如某個信號處理器中又產(chǎn)生了一個信號,會使得信號的處理像一棵樹一樣的展開。
四.基于QT4的一個多線程工程實現(xiàn)(網(wǎng)上見到的一個比較好的例子,關(guān)于線程任務(wù)分配) (網(wǎng)址:http://easons.blogbus.com/logs/14845035.html)
想法:需要模仿ACE異步調(diào)用的方法,在一個線程分配任務(wù)給工作線程,并等待工作線程完成后返回結(jié)果。
定義一個線程類:
頭文件:
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include <QThread>
#include <QEvent>
#define METHOD_EVENT QEvent::User + 1028
class MethodEvent : public QEvent
{
public:
MethodEvent() : QEvent(QEvent::Type(METHOD_EVENT))
{
}
~MethodEvent()
{
}
public:
int i;//存儲返回值!
};
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
~MyThread();
bool StartThread();
bool StopThread();
protected:
void run();
void customEvent(QEvent * e);
};
#endif // MYTHREAD_H
實現(xiàn)文件:
#include "mythread.h"
MyThread::MyThread()
{
}
MyThread::~MyThread()
{
}
bool MyThread::StartThread()
{
start();//線程啟動,調(diào)用run
return true;
}
bool MyThread::StopThread()
{
exit();
return false;
}
void MyThread::run()
{
exec();//進入本線程的消息循環(huán)
}
void MyThread::customEvent(QEvent * e)
{
if(0 == e)
return;
if( METHOD_EVENT != e->type() )
{
return;
}
MethodEvent* methodEvent = static_cast<MethodEvent*>(e);
if(NULL == methodEvent)
return;
for (int i = 0; i < 1000000000; ++i)
{
methodEvent->i = i;
}
}
主函數(shù):
#include <QtCore/QCoreApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread mythread;
mythread.StartThread();//啟動輔助線程,
MethodEvent event;//新建一個任務(wù)
QCoreApplication::sendEvent(&mythread, &event);//發(fā)給工作線程,并阻塞等待
int result = event.i;//得到返回值。
return a.exec();
}
說明:如果不用到返回值,也就是只是簡單分配任務(wù)的話,可以用postEvent的方法,也就是要new一個event,發(fā)給工作線程,工作線程處理完事件后會去刪除你new的event,主線程就不管了。換句話說,postEvent馬上返回,不管event是否被處理完,兩個線程同時在工作。而上面是主線程要等待工作線程完成工作后才可以繼續(xù),阻塞在sendEvent上了
五.圖像循環(huán)隊列(攝像頭的采集數(shù)據(jù)放到圖像循環(huán)隊列)
程序通過建立帶共享鎖的4幀圖像循環(huán)隊列做為圖像采集線程和圖像發(fā)送線程進行數(shù)據(jù)交換的公共緩沖區(qū)(帶共享鎖的循環(huán)隊列在這個網(wǎng)址下有介紹:http://www.zaoxue.com/article/tech-55122.htm)
能夠在通信中更好的對數(shù)據(jù)進行讀寫和存儲,在程序編寫過程中就把數(shù)據(jù)隊列的方式改為了循環(huán)隊列。通過設(shè)定數(shù)據(jù)存儲地址的頭指針和尾指針,以及對數(shù)據(jù)存儲長度狀態(tài)值的判斷,從而達到循環(huán)隊列的目的。當(dāng)有新的數(shù)據(jù)到來時,數(shù)據(jù)的尾指針自動往后移,長度的也做相應(yīng)的增加。同時判斷數(shù)據(jù)的長度有沒有超出BANK的地址范圍,如果超出地址范圍,則尾指針跳轉(zhuǎn)到頭指針之前,繼續(xù)往后存儲數(shù)據(jù),形成了循環(huán)隊列;同樣,當(dāng)從RAM里取走數(shù)據(jù)時,數(shù)據(jù)的頭指針自動往后移,長度的也做相應(yīng)的增減。在隊列的出隊操作中,還要對數(shù)據(jù)的長度進行判斷,如果達到了最大長度,則丟棄后面所有的數(shù)據(jù)。直到緩存區(qū)能有空間繼續(xù)作隊列為止。
六.視頻采集數(shù)據(jù)緩沖機制的研究
在視頻采集系統(tǒng)中,視頻數(shù)據(jù)的采集與發(fā)送需要很好的性能,所以需要一個高性能的數(shù)據(jù)流緩沖機制,在傳統(tǒng)的生產(chǎn)者/消費者模型的基礎(chǔ)上,提出一種新的數(shù)據(jù)流緩沖模型(在一份叫做
視頻會議系統(tǒng)中數(shù)據(jù)緩沖機制的研究.pdf中有介紹)(一個關(guān)于多線程同步的文章 :http://www.vckbase.com/document/viewdoc/?id=1080)七.GNU/Linux中解決多線程互斥同步問題的分析和說明(
http://blog.chinaunix.net/u1/35100/showart.php?id=274716)
當(dāng)解決多線程互斥同步的問題時,經(jīng)常會有如下幾個問題:
1. 在一個給定的問題中,需要多少個Mutex,多少個Semaphore?有什么規(guī)律
2. 在對臨界區(qū)加鎖和等待信號量的順序上有什么要求和規(guī)律
3. 什么樣操作適合放在臨界區(qū),什么樣的不適合
下面就生產(chǎn)者和消費者問題來分析一些這幾個問題.
下面是一個簡單的實現(xiàn)程序:
生產(chǎn)者向數(shù)組sharedArray中寫入數(shù)據(jù),而消費者從該數(shù)組中讀取數(shù)據(jù).
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /*共享緩沖區(qū)的大小*/
int sharedArray[MAXSIZE]; /*sharedArray是共享緩沖區(qū)*/
int curr=-1; /*curr是用來指定sharedArray當(dāng)前存有數(shù)據(jù)的最大位置*/
/*注意,sharedArray和curr都屬于共享數(shù)據(jù)*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /*鎖定臨界區(qū)的mutex*/
sem_t waitNonEmpty, waitNonFull; /*等待"非空資源"和等待"非滿資源"的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /*是否有"非空資源"*/
pthread_mutex_lock(&sharedMutex); /*進入臨界區(qū)*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, /n", (char*)whichone, position, data);
sem_post(&waitNonFull); /*生成一個"非滿資源"*/
pthread_mutex_unlock(&sharedMutex); /*離開臨界區(qū)*/
sleep(2); /*跟同步無關(guān)的費時操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /*生成一個隨機數(shù)據(jù),注意是10.0而不是10*/
sem_wait(&waitNonFull); /*是否有"非滿資源"*/
pthread_mutex_lock(&sharedMutex); /*進入臨界區(qū)*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, /n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /*生成一個"非空資源"*/
pthread_mutex_unlock(&sharedMutex); /*離開臨界區(qū)*/
sleep(1); /*跟同步無關(guān)的費時操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /*兩個生產(chǎn)者和兩個消費者*/
sem_init(&waitNonEmpty, 0, empty); /*初始化信號量*/
sem_init(&waitNonFull, 0, full);
/*注意,本問題中的兩種semaphore是有一定關(guān)系的,那就是它們的初始值之和應(yīng)該等于共享緩沖區(qū)大小*/
/*即empty+full等于MAXSIZE*/
pthread_create (&consumer1, NULL, &readData, "consumer1");
pthread_create (&consumer2, NULL, &readData, "consumer2");
pthread_create (&producer1, NULL, &writeData, "producer1");
pthread_create (&producer2, NULL, &writeData, "producer2");
pthread_join (consumer1, NULL);
pthread_join (consumer2, NULL);
pthread_join (producer1, NULL);
pthread_join (producer2, NULL);
sem_destroy(&waitNonEmpty);
sem_destroy(&waitNonFull);
}
分析和說明:
1. 在一個給定的問題中,需要多少個Mutex,多少個Semaphore?有什么規(guī)律
在本問題中,共需要一個Mutex和兩個Semaphore.
其中,Mutex是用來鎖定臨界區(qū)的,以解決對共享數(shù)據(jù)的互斥訪問問題(無論是對生成者還是對消費者);
我們共需要兩個Semaphore,這是因為在本問題中共有兩個稀缺資源.
第一種是"非空"這種資源,是在消費者之間進行競爭的.
第二種是"非滿"這種資源,是在生產(chǎn)者之間進行競爭的.
所以,一般來說,需要鎖定臨界區(qū),就需要Mutex;有幾種稀缺資源就需要幾個Semaphore.
對稀缺資源的分析不能想當(dāng)然.稀缺資源不一定是指被共享的資源,很多時候是指線程會被阻塞的條件(除了要進臨界區(qū)被阻塞外).
本例中,消費者會在緩沖區(qū)為空時被阻塞,所以"非空"是一種稀缺資源;
生產(chǎn)者會在緩沖區(qū)為滿時被阻塞,所以"非滿"也是一種稀缺資源.
2. 在對臨界區(qū)加鎖和等待信號量的順序上有什么要求和規(guī)律
這里要說兩點:
第一,不要將等待信號量的語句放在被鎖定的臨界區(qū)內(nèi),這樣會造成死鎖.而且這也是很沒有必要的.
比如,消費者在緩沖區(qū)沒有數(shù)據(jù)的時候進入臨界區(qū),這樣就會把臨界區(qū)鎖上,由于沒有數(shù)據(jù),消費者也會被鎖上.
這時,任何生產(chǎn)者都會由于臨界區(qū)被鎖上而被block住,這樣就造成了死鎖.
第二,如果有多個Semaphore需要等待,那么每個線程中,最好對這多個信號量進行等待的順序一致,
不然的話很容易造成死鎖.
3. 什么樣操作適合放在臨界區(qū),什么樣的不適合
一般來說,臨界區(qū)中只放對共享數(shù)據(jù)進行訪問的語句,這樣會改善程序的性能.
很多時候,取出共享數(shù)據(jù)的副本后,對副本進行費時的各種操作就不需要放在臨界區(qū)了.
比如,本例中的sleep語句就根本不需要放入臨界區(qū).
八.教你如何測試循環(huán)緩沖區(qū)(代碼我是參考DivX播放器源代碼-playa-0.3.3src.zip這個開源軟件的)
這個緩沖區(qū)主要包括這兩個文件:Queue.h,Queue.cpp(具體代碼請參考程序)
主要函數(shù):
RingBuff::RingBuff()
{
read_pos = 0;
write_pos = 0;
}
void RingBuff::ring_read(unsigned char *data, int size)
{
MutexBuff.lock();
if(write_pos <= read_pos) {
if(read_pos + size < RING_SIZE) {
memcpy(data, ring + read_pos, size);
read_pos += size;
}
else {
if(read_pos + size < RING_SIZE + write_pos) {
unsigned int before, after;
before = (RING_SIZE - 1) - read_pos;
after = size - before;
memcpy(data, ring + read_pos, before);
memcpy(data + before, ring, after);
read_pos = after;
}
else {
}
}
}
else {
if(read_pos + size <= write_pos) {
memcpy(data, ring + read_pos, size);
read_pos += size;
}
else {
}
}
MutexBuff.unlock();
}
void RingBuff::ring_write(unsigned char *data, int size)
{
MutexBuff.lock();
if(write_pos >= read_pos) {
if(write_pos + size < RING_SIZE) {
memcpy(ring + write_pos, data, size);
write_pos += size;
}
else {
if(write_pos + size < RING_SIZE + read_pos) {
unsigned int before, after;
before = (RING_SIZE - 1) - write_pos;
after = size - before;
memcpy(ring + write_pos, data, before);
memcpy(ring, data + before, after);
write_pos = after;
}
}
}
else {
if(write_pos + size <= read_pos) {
memcpy(ring + write_pos, data, size);
write_pos += size;
}
MutexBuff.unlock();
return;
}
MutexBuff.unlock();
}
int RingBuff::ring_full(int size)
{
if(write_pos == read_pos)
return 0;
if(write_pos > read_pos) {
if(write_pos + size < read_pos + RING_SIZE)
return 0;
return 1;
}
else {
if(write_pos + size < read_pos)
return 0;
return 1;
}
}
void RingBuff::CleanUp()
{
if(ring)
delete []ring;
// ring = 0;
write_pos = read_pos = 0;
}
測試自己所建的兩個視頻采集緩沖區(qū):(這個是應(yīng)用在多線程系統(tǒng)中的,但測試是用于一個線程中全部執(zhí)行的,測試是沒有問題的,但如果把“從每二個視頻緩沖區(qū)中取出視頻數(shù)據(jù)--->把從第二個視頻緩沖區(qū)中取出的視頻數(shù)據(jù)顯示出來”這部分放到解壓線程中測試時是可以顯示圖像的,但顯示的圖像的顏色變了,我想應(yīng)該是緩沖區(qū)的QMutex 問題)
//第一種測試(在同一個線程中執(zhí)行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//將視頻數(shù)據(jù)放入到每一個緩沖區(qū)
parent->readCapBuff(testbuffer,320*240*4);//從每一個緩沖區(qū)中取得視頻數(shù)據(jù)
parent->writeEnCodebuff(testbuffer,320*240*4);//把從第一個緩沖區(qū)中取得的視頻數(shù)據(jù)放到第二個視頻緩沖區(qū)中
parent->readEnCodebuff(testbuffer2,320*240*4);//從每二個視頻緩沖區(qū)中取出視頻數(shù)據(jù)
//add--->>把從第二個視頻緩沖區(qū)中取出的視頻數(shù)據(jù)顯示出來
//QImage image(testbuffer,320,240,QImage::Format_RGB32);
//QImage image(pBuffer,320,240,QImage::Format_RGB32);
QImage image(testbuffer2,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
parent->label->setPixmap(pixmap);
parent->label->setFixedSize(pixmap.width(),pixmap.height());
//add
}
}
//第二種情況(在兩個線程中分別執(zhí)行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//將視頻數(shù)據(jù)放入到每一個緩沖區(qū)
parent->readCapBuff(testbuffer,320*240*4);//從每一個緩沖區(qū)中取得視頻數(shù)據(jù)
parent->writeEnCodebuff(testbuffer,320*240*4);//把從第一個緩沖區(qū)中取得的視頻數(shù)據(jù)放到第二個視頻緩沖區(qū)中
}
}
void CXvidDec::run()
{for(;;){
parent->readEnCodebuff(getEnCodeBuff,320*240*4);
//Decode(getEnCodeBuff, 320*240*4) ;
//v4l_save_pnm(m_image, 320, 240, 3);
//add_display
//QImage image(m_image,320,240,QImage::Format_RGB32);
QImage image(getEnCodeBuff,320,240,QImage::Format_RGB32);
QPixmap pixmap;
pixmap=pixmap.fromImage(image);
parent->label->setPixmap(pixmap);
parent->label->setFixedSize(pixmap.width(),pixmap.height());
//ddd_display
}
}
我們要測試 ring_read()與ring_write()是否符合我們的要求,本人設(shè)計了一種簡單的測試方法:就是用ring_write()把采集到的視頻數(shù)據(jù)放到緩沖區(qū)中,然后用ring_read()從緩沖區(qū)中讀取數(shù)據(jù),然后將數(shù)據(jù)保存成一張圖片,如果圖片是輸入的圖像就證明了緩沖區(qū)的代碼是正確的,由于我使用的隊列緩沖區(qū)完全是由生產(chǎn)者驅(qū)動的,就是說隊列的推進的速度等于生產(chǎn)者生產(chǎn)產(chǎn)品的速度,消費者不一定必必須消費每一個產(chǎn)品,在一定程序上,丟失某些數(shù)據(jù)是允許的
循環(huán)緩沖區(qū)由一個固定大小的內(nèi)存緩沖區(qū)構(gòu)成,進程使用這個內(nèi)存緩沖區(qū)進行日志記錄。顧名思義,該緩沖區(qū)采用循環(huán)的方式進行實現(xiàn)。當(dāng)該緩沖區(qū)填滿了數(shù)據(jù)時,無需為新的數(shù)據(jù)分配更多的內(nèi)存,而是從緩沖區(qū)開始的位置對其進行寫操作,因此將覆蓋以前的內(nèi)容。
1)對循環(huán)緩沖區(qū)進行寫操作
2)注意事項---在多線程程序中使用循環(huán)緩沖區(qū)
這個部分介紹了在多線程應(yīng)用程序中使用循環(huán)緩沖區(qū)啟時需要考慮的一些重要方面。
在訪問一個公共的資源時,同步 始終是多線程程序不可缺少的部分。因為每個線程都試圖對全局空間進行寫操作,所以必須確保它們同步地寫入內(nèi)存,否則消息就會遭到破壞。通常,每個線程在寫入緩沖區(qū)之前都持有一個鎖,在完成操作時釋放該鎖。您可以下載一個使用鎖對內(nèi)存進行寫操作的循環(huán)緩沖區(qū)示例。
這種方法具有以下的缺點:如果您的應(yīng)用程序中包含幾個線程,并且每個線程都在進行訪問緩沖區(qū),那么該進程的整體性能將會受到影響,因為這些線程將在獲得和釋放鎖上花費了大部分的時間。
通過使得每個線程將數(shù)據(jù)寫入到它自己的內(nèi)存塊,就可以完全避免同步問題。當(dāng)收到來自用戶的轉(zhuǎn)儲數(shù)據(jù)的請求時,每個線程獲得一個鎖,并將其轉(zhuǎn)儲到中心 位置。因為僅在將數(shù)據(jù)刷新到磁盤時獲得鎖,所以性能并不會受到很大的影響。
九.每二種緩沖方法,先建多個buffer,然后將這些buffer構(gòu)成bufferpool(本人覺得這種方法比較好)
class CBuffer
{
BYTE * m_pbBuffer ; // buffer pointer for data
DWORD m_dwBufferLength ; // allocated buffer length
DWORD m_dwPayloadLength ; // actual data length; <= allocated
LONG m_lRef ; // this object's ref; 0 when we're available
CBufferPool * m_pBufferPool ; // back pointer
DWORD_PTR m_dwCompletionContext ; // anything
LIST_ENTRY m_ListEntry ; // list's link
OVERLAPPED m_Overlapped ; // OVERLAPPED struct we use
public :
CBuffer (
IN CBufferPool * pBufferPool, // back pointer
IN DWORD dwBufferLength, // how much to allocator
OUT HRESULT * phr // success/failre of init
) ;
~CBuffer (
) ;
// LIST_ENTRY manipulation
void InsertHead (IN LIST_ENTRY * pListHead) { ASSERT (IsListEmpty (& m_ListEntry)) ; InsertHeadList (pListHead, & m_ListEntry) ; }
void Unhook () { RemoveEntryList (& m_ListEntry) ; InitializeListHead (& m_ListEntry) ; }
// returns a pointer to the object's OVERLAPPED struct
OVERLAPPED * GetOverlapped () { return & m_Overlapped ; }
// given a LIST_ENTRY, recovers the hosting CBuffer object
static CBuffer * RecoverCBuffer (IN LIST_ENTRY * pListEntry) { CBuffer * pBuffer = CONTAINING_RECORD (pListEntry, CBuffer, m_ListEntry) ;
return pBuffer ; }
// buffer manipulation
BYTE * GetBuffer () { return m_pbBuffer ; }
void SetBuffer (BYTE *pBuffer)
{
if(this->m_pbBuffer != NULL)
delete [] m_pbBuffer;
m_pbBuffer = pBuffer;
}
DWORD GetBufferLength () { return m_dwBufferLength ; }
DWORD GetPayloadLength () { return m_dwPayloadLength ; }
void SetPayloadLength (IN DWORD dw) { ASSERT (dw <= m_dwBufferLength) ; m_dwPayloadLength = dw ; }
// async IO completion context; allows us to store information that
// allows us to recover when the IO completes
void SetCompletionContext (IN DWORD_PTR dw) { m_dwCompletionContext = dw ; }
DWORD_PTR GetCompletionContext () { return m_dwCompletionContext ; }
// refcounting
ULONG AddRef () { return InterlockedIncrement (& m_lRef) ; }
ULONG
Release (
) ;
} ;
class CBufferPool
{
// struct is used to request a CBuffer object; the buffer pool maintains a
// pool of these structs to queue buffer requests when none are available.
struct BLOCK_REQUEST {
LIST_ENTRY ListEntry ;
HANDLE hEvent ;
CBuffer * pBuffer ;
} ;
LIST_ENTRY m_Buffers ; // CBuffer list
LIST_ENTRY m_RequestPool ; // BLOCK_REQUEST list; pool
LIST_ENTRY m_Request ; // BLOCK_REQUEST list; outstanding
CRITICAL_SECTION m_crt ; // lock to access the various lists
DWORD m_dwBufferAllocatedLength ; // allocated length of each
void Lock_ () { EnterCriticalSection (& m_crt) ; }
void Unlock_ () { LeaveCriticalSection (& m_crt) ; }
// gets a request object; must hold the pool lock
BLOCK_REQUEST *
GetRequestLocked_ (
)
{
LIST_ENTRY * pListEntry ;
BLOCK_REQUEST * pBlockRequest ;
if (IsListEmpty (& m_RequestPool) == FALSE) {
// list of unused is not empty; grab one
pListEntry = RemoveHeadList (& m_RequestPool) ;
pBlockRequest = CONTAINING_RECORD (pListEntry, BLOCK_REQUEST, ListEntry) ;
}
else {
// list is empty; must allocate
pBlockRequest = new BLOCK_REQUEST ;
}
// initialize correctly if we got 1
if (pBlockRequest) {
pBlockRequest -> hEvent = NULL ;
pBlockRequest -> pBuffer = NULL ;
}
return pBlockRequest ;
}
// recycles the given block request
void
RecycleRequestLocked_ (
IN BLOCK_REQUEST * pBlockRequest
)
{
InsertHeadList (& m_RequestPool, & pBlockRequest -> ListEntry) ;
}
public :
CBufferPool (
IN DWORD dwPoolSize, // number of buffers to allocate
IN DWORD dwBufferLength, // allocated length of each buffer
OUT HRESULT * phr // success/failure
) ;
~CBufferPool (
) ;
DWORD GetBufferAllocatedLength () { return m_dwBufferAllocatedLength ; }
void
Recycle (
CBuffer *
) ;
CBuffer *
GetBuffer (
IN HANDLE hEvent, // manual reset
IN DWORD dwTimeout = INFINITE
) ;
} ;
#endif // __buffpool_h
以上這些代碼是我宿舍的成哥所寫的,聽說是從directshow下的例子所帶的,我就參考了這個自己再搞一個qt下的,以下代碼是我自己寫的
buffer.h文件
#include <QMutex>
#define MAXBUFSIZE 320*240*4
class Buffer
{
public:
Buffer();
virtual ~Buffer();
char *getBuf();
int getSize();
void setSize(int s);
//int getTag();
//void setTag(int stop);
void lockBuf();
void unlockBuf();
int capacity();
int getAvailableSpace();
//unsigned char*buf;
unsigned char *buf;
//char buf[MAXBUFSIZE];
int size;
//synchronization
//#ifdef WIN32
// CCriticalSection mutex;
//#else
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//#endif
// int stopTag; //flag to indicate I/O operation stops or fails on this buffer
public:
//unsigned char* m_pbBuffer;
int m_dwBufferLength ;
int m_dwPayloadLength ;
QMutex BufferMutex;
};
buffer.cpp文件
#include "buffer.h"
#include <string.h>
#include <malloc.h>
Buffer::Buffer():m_dwBufferLength(MAXBUFSIZE)
{
buf = (unsigned char*)malloc(m_dwBufferLength) ;
}
Buffer::~Buffer()
{
delete buf ;
}
char* Buffer::getBuf()
{
//return &buf[0];
}
int Buffer::getSize()
{
return size;
}
void Buffer::setSize(int s)
{
size = s;
}
//int Buffer::getTag()
//
// return stopTag;
//}
//void Buffer::setTag(int stop)
//{
// stopTag = stop;
//}
int Buffer::capacity()
{
return sizeof(buf);
}
int Buffer::getAvailableSpace()
{
return sizeof(buf)-size;
}
void Buffer::lockBuf()
{
BufferMutex.lock();
}
void Buffer::unlockBuf()
{
BufferMutex.unlock();
}
QueueBuffer.h文件
#include <QMutex>
#include <QWaitCondition>
#include "buffer.h"
#define BUFFERNUM 5
class BufferQueue
{
public:
BufferQueue();
virtual ~BufferQueue();
Buffer *getReadBuffer();
void getWriteBuffer(unsigned char *e,int len);
int IsEmpty() const {return readPos == writePos ;}//&& tag == 0; } //if Queue is empty
int IsFull() const {return readPos == (writePos+1)%BUFFERNUM;}//&&tag ==1; //if Queue is full
//bool moveReadBuffer(bool);
//bool moveWriteBuffer(bool);
//void invalidate();
QMutex QueueBufferMutex;
QWaitCondition bufferNotEmpty;//用于信號等待
QWaitCondition bufferNotFull;//用于信號等待
int numUsedBytes;
void lockAccessMutex();
void unLockAccessMutex();
Buffer buffers[BUFFERNUM];
int readPos;//int front;
int writePos;//int rear;
int num;
bool validFlag;
/*Define two events to synchronize between input thread and output thread
**hFullEvent is defined for the circumstance when the buffer queue is full
**and input thread is waiting for output thread to retrieve data from buffer queue
**hEmptyEvent is defined for the circumstance when the buffer queue is empty
**and output thread is waiting for input thread puts data in buffer queue
*/
static int numOfBuffers(){return BUFFERNUM;}
};
QueueBuffer.cpp文件
#include "QueueBuffer.h"
#include <QThread>
BufferQueue::BufferQueue()
{
readPos=writePos=NULL;
numUsedBytes = 0;
}
BufferQueue::~BufferQueue()
{
}
void BufferQueue::lockAccessMutex()
{
QueueBufferMutex.lock();
}
void BufferQueue::unLockAccessMutex()
{
QueueBufferMutex.unlock();
}
/*
* This is used by read thread
*/
Buffer* BufferQueue::getReadBuffer()
{
Buffer *readBuffer;
QueueBufferMutex.lock();
if (numUsedBytes == 0)
bufferNotEmpty.wait(&QueueBufferMutex);
QueueBufferMutex.unlock();
QueueBufferMutex.lock();
readBuffer=&buffers[readPos];
readPos = (readPos+1)%numOfBuffers();
--numUsedBytes;
bufferNotFull.wakeAll();
QueueBufferMutex.unlock();
return readBuffer;
}
void BufferQueue::getWriteBuffer(unsigned char *e,int len)
{
Buffer *writeBuffer = NULL;
QueueBufferMutex.lock();
if (numUsedBytes == numOfBuffers())
bufferNotFull.wait(&QueueBufferMutex);
QueueBufferMutex.unlock();
QueueBufferMutex.lock();
buffers[writePos].buf=e;
buffers[writePos].m_dwPayloadLength=len;
writePos=(writePos+1)%numOfBuffers();
++numUsedBytes;
bufferNotEmpty.wakeAll();
QueueBufferMutex.unlock();
}
十.在qt下如何訪問一個共享對象呢?
這是一個我不斷思考的問題:有一個共享的視頻采集緩沖區(qū)(采集線程負(fù)責(zé)把視頻數(shù)據(jù)放入緩沖區(qū),壓縮線程負(fù)責(zé)從緩沖區(qū)中取出視頻數(shù)據(jù)再壓縮),到底這個全局的視頻采集緩沖區(qū)如何可以使采集線程和壓縮線程共享呢,開始時我是把這個視頻采集緩沖區(qū)對象放到采集線程中的,這樣采集線程就能訪問到這個視頻采集緩沖區(qū),但是壓縮線程如何共享這個視頻采集緩沖區(qū)呢?(由于這個壓縮線程要讀這個視頻緩沖區(qū)要用到視頻緩沖對象的一個讀函數(shù),但這個視頻緩沖對象是在采集線程中定義的,壓縮線程是不能訪問采集線程下的子對象(視頻采集緩沖區(qū))這成了一個問題),經(jīng)過兩天的思考終于找到了一種辦法(由于qt跟vc的編程方法有很大不同,加上網(wǎng)上的資料比較少),我決定把視頻緩沖區(qū)對象放到主線程(界面對象)中,然后在采集線程和壓縮線程中加上一個主線程對像(通過這個對象就能方便地使采集線程和壓縮線程訪問共享這個視頻采集緩沖區(qū)).代碼如下
MainWindow::MainWindow()//主線程
{
setWindowTitle(tr("No movie loaded"));
setMinimumSize(320,240);
resize(520,420);
label=new QLabel(this);
setCentralWidget(label);
label->setBackgroundRole(QPalette::Dark);
btn_start = new QPushButton("start",this);
btn_start ->setGeometry( QRect(50, 300, 80, 40)) ;
connect(btn_start,SIGNAL(clicked()),this, SLOT(save())) ;
piturebuf = (unsigned char *)malloc(320*240*4);
capbuff=new RingBuff();//新建一個視頻采集緩沖區(qū)
m_enc = new CXvidEnc(this) ; //新建一個壓縮對象(注意this)
m_enc->AttachCaller(320, 240) ;
CXvidEnc::XVID_GLOBAL_INIT() ;
m_enc->Open() ;
m_enc->start();//啟動壓縮線程
a = new CapThread(this);//新建一個采集對象(注意this)
a->start();//啟動采集線程
};
//主線程中對視頻采集緩沖區(qū)的操作
void MainWindow::readCapBuff(unsigned char *data, int size)
{
//unsigned char*piturebuf = (unsigned char *)malloc(320*240*4) ;
capbuff->ring_read(data,size);
}
void MainWindow::writeCapBuff(unsigned char *data, int size)
{
capbuff->ring_write(data,size);
}
//采集線程
CapThread::CapThread(MainWindow *parent){
this->parent = parent;(與上面的this對應(yīng))
v4l_open(DEFAULT_DEVICE, &v4l_dev) ;
v4l_get_capability(&v4l_dev);
v4l_set_picture( &v4l_dev );
v4l_get_picture(&v4l_dev);
v4l_init_mbuf(&v4l_dev);
v4l_get_mbuf(&v4l_dev);
v4l_dev.picture.contrast = 110000;
v4l_set_picture(&v4l_dev);
}
//采集線程執(zhí)行的任務(wù) (把采集到的數(shù)據(jù)pBuffer放到視頻采集緩沖區(qū)中)
void CapThread::run()
{
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*3);(這樣就可以訪問到共享的視頻采集緩沖區(qū)了)
}
}
//壓縮線程
CXvidEnc::CXvidEnc(MainWindow *parent)
{
this->parent = parent;
m_closed = true ;
//m_enc_caller = NULL ;
m_enc_handle = NULL ;
m_key = 0 ;
m_width = 0 ;
m_height = 0 ;
m_bitstream = NULL ;
getCapBuff = (unsigned char *)malloc(320*240*4);
}
//壓縮線程要執(zhí)行的任務(wù)(從視頻緩沖區(qū)中取出數(shù)據(jù)并壓縮)
void CXvidEnc::run()
{
for(;;){
parent->readCapBuff(getCapBuff, 320*240*4);
Encode(getCapBuff);
}
}
十一.
QT多線程入門文章 (轉(zhuǎn))Qt 中的多線程(一)
QT通過三種形式提供了對線程的支持。它們分別是,一、平臺無關(guān)的線程類,二、線程安全的事件投遞,三、跨線程的信號-槽連接。這使得開發(fā)輕巧的多 線程Qt程序更為容易,并能充分利用多處理器機器的優(yōu)勢。多線程編程也是一個有用的模式,它用于解決執(zhí)行較長時間的操作而不至于用戶界面失去響應(yīng)。在Qt 的早期版本中,在構(gòu)建庫時有不選擇線程支持的選項,從4.0開始,線程總是有效的。
線程類
Qt 包含下面一些線程相關(guān)的類:
QThread 提供了開始一個新線程的方法
QThreadStorage 提供逐線程數(shù)據(jù)存儲
QMutex 提供相互排斥的鎖,或互斥量
QMutexLocker 是一個便利類,它可以自動對QMutex加鎖與解鎖
QReadWriterLock 提供了一個可以同時讀操作的鎖
QReadLocker與QWriteLocker 是便利類,它自動對QReadWriteLock加鎖與解鎖
QSemaphore 提供了一個整型信號量,是互斥量的泛化
QWaitCondition 提供了一種方法,使得線程可以在被另外線程喚醒之前一直休眠。
創(chuàng)建一個線程
為創(chuàng)建一個線程,子類化QThread并且重寫它的run()函數(shù),例如:
class MyThread : public QThread
{
Q_OBJECT protected:
void run();
};
void MyThread::run()
{
...
}
之后,創(chuàng)建這個線程對象的實例,調(diào)用QThread::start()。于是,在run()里出現(xiàn)的代碼將會在另外線程中被執(zhí)行。
注意:QCoreApplication::exec()必須總是在主線程(執(zhí)行main()的那個線程)中被調(diào)用,不能從一個QThread中調(diào)用。在 GUI程序中,主線程也被稱為GUI線程,因為它是唯一一個允許執(zhí)行GUI相關(guān)操作的線程。另外,你必須在創(chuàng)建一個QThread之前創(chuàng)建 QApplication(orQCoreApplication)對象。
線程同步
QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了線程同步的手段。使用線程的主要想法是希望它們可以盡可能并發(fā)執(zhí)行,而一些關(guān)鍵點上線程之間需要停止或等待。例如,假如兩個線程試圖同時訪問同一個 全局變量,結(jié)果可能不如所愿。
QMutex 提供相互排斥的鎖,或互斥量。在一個時刻至多一個線程擁有mutex,假如一個線程試圖訪問已經(jīng)被鎖定的mutex,那么它將休眠,直到擁有mutex的線程對此mutex解鎖。Mutexes常用來保護共享數(shù)據(jù)訪問。
QReadWriterLock 與QMutex相似,除了它對 "read","write"訪問進行區(qū)別對待。它使得多個讀者可以共時訪問數(shù)據(jù)。使用QReadWriteLock而不是QMutex,可以使得多線程程序更具有并發(fā)性。
QReadWriteLock lock;
void ReaderThread::run()
{
// ...
lock.lockForRead();
read_file();
lock.unlock();
//...
}
void WriterThread::run()
{
// ...
lock.lockForWrite();
write_file();
lock.unlock();
// ...
}
QSemaphore 是QMutex的一般化,它可以保護一定數(shù)量的相同資源,與此相對,一個mutex只保護一個資源。下面例子中,使用QSemaphore來控制對環(huán)狀緩 沖的訪問,此緩沖區(qū)被生產(chǎn)者線程和消費者線程共享。生產(chǎn)者不斷向緩沖寫入數(shù)據(jù)直到緩沖末端,再從頭開始。消費者從緩沖不斷讀取數(shù)據(jù)。信號量比互斥量有更好 的并發(fā)性,假如我們用互斥量來控制對緩沖的訪問,那么生產(chǎn)者,消費者不能同時訪問緩沖。然而,我們知道在同一時刻,不同線程訪問緩沖的不同部分并沒有什么 危害。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QSemaphore freeBytes(BufferSize);
QSemaphore usedBytes;
class Producer : public QThread
{
public:
void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i)
{
freeBytes.acquire();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
usedBytes.release();
}
}
class Consumer : public QThread
{
public: void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i)
{
usedBytes.acquire();
fprintf(stderr, "%c", buffer[i % BufferSize]);
freeBytes.release();
}
fprintf(stderr, "/n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
QWaitCondition 允許線程在某些情況發(fā)生時喚醒另外的線程。一個或多個線程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()設(shè)置一個條件。wakeOne()隨機喚醒一個,wakeAll()喚醒所有。
下面的例子中,生產(chǎn)者首先必須檢查緩沖是否已滿(numUsedBytes==BufferSize),如果是,線程停下來等待 bufferNotFull條件。如果不是,在緩沖中生產(chǎn)數(shù)據(jù),增加numUsedBytes,激活條件bufferNotEmpty。使用mutex來 保護對numUsedBytes的訪問。另外,QWaitCondition::wait()接收一個mutex作為參數(shù),這個mutex應(yīng)該被調(diào)用線程 初始化為鎖定狀態(tài)。在線程進入休眠狀態(tài)之前,mutex會被解鎖。而當(dāng)線程被喚醒時,mutex會處于鎖定狀態(tài),而且,從鎖定狀態(tài)到等待狀態(tài)的轉(zhuǎn)換是原子 操作,這阻止了競爭條件的產(chǎn)生。當(dāng)程序開始運行時,只有生產(chǎn)者可以工作。消費者被阻塞等待bufferNotEmpty條件,一旦生產(chǎn)者在緩沖中放入一個 字節(jié),bufferNotEmpty條件被激發(fā),消費者線程于是被喚醒。
const int DataSize = 100000;
const int BufferSize = 8192;
char buffer[BufferSize];
QWaitCondition bufferNotEmpty;
QWaitCondition bufferNotFull;
QMutex mutex; int numUsedBytes = 0;
class Producer : public QThread
{
public: void run();
};
void Producer::run()
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i)
{
mutex.lock();
if (numUsedBytes == BufferSize) bufferNotFull.wait(&mutex);
mutex.unlock();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
mutex.lock();
++numUsedBytes;
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
class Consumer : public QThread
{
public: void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) { mutex.lock(); if (numUsedBytes == 0) bufferNotEmpty.wait(&mutex);
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "/n");
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Producer producer;
Consumer consumer;
producer.start();
consumer.start();
producer.wait();
consumer.wait();
return 0;
}
Qt 中的多線程(二) 可重入與線程安全 在Qt文檔中,術(shù)語“可重入”與“線程安全”被用來說明一個函數(shù)如何用于多線程程序。假如一個類的任何函數(shù)在此類的多個不同的實例上,可以被多個線程同時 調(diào)用,那么這個類被稱為是“可重入”的。假如不同的線程作用在同一個實例上仍可以正常工作,那么稱之為“線程安全”的。 大多數(shù)c++類天生就是可重入的,因為它們典型地僅僅引用成員數(shù)據(jù)。任何線程可以在類的一個實例上調(diào)用這樣的成員函數(shù),只要沒有別的線程在同一個實例上調(diào) 用這個成員函數(shù)。舉例來講,下面的Counter 類是可重入的:
class Counter
{
public:
Counter() {n=0;}
void increment() {++n;}
void decrement() {--n;}
int value() const {return n;}
private:
int n;
};
這個類不是線程安全的,因為假如多個線程都試圖修改數(shù)據(jù)成員 n,結(jié)果未定義。這是因為c++中的++和--操作符不是原子操作。實際上,它們會被擴展為三個機器指令: 1,把變量值裝入寄存器 2,增加或減少寄存器中的值 3,把寄存器中的值寫回內(nèi)存 假如線程A與B同時裝載變量的舊值,在寄存器中增值,回寫。他們寫操作重疊了,導(dǎo)致變量值僅增加了一次。很明顯,訪問應(yīng)該串行化:A執(zhí)行123步驟時不應(yīng) 被打斷。使這個類成為線程安全的最簡單方法是使用QMutex來保護數(shù)據(jù)成員:
class Counter {
public:
Counter() { n = 0; }
void increment()
{
QMutexLocker locker(&mutex);
++n;
}
void decrement() {
QMutexLocker locker(&mutex); --n;
}
int value() const
{
QMutexLocker locker(&mutex);
return n;
}
private:
mutable QMutex mutex;
int n;
};
QMutexLocker類在構(gòu)造函數(shù)中自動對mutex進行加鎖,在析構(gòu)函數(shù)中進行解鎖。隨便一提的是,mutex使用了mutable關(guān)鍵字來修飾, 因為我們在value()函數(shù)中對mutex進行加鎖與解鎖操作,而value()是一個const函數(shù)。 大多數(shù)Qt類是可重入,非線程安全的。有一些類與函數(shù)是線程安全的,它們主要是線程相關(guān)的類,如QMutex,QCoreApplication:: postEvent()。 線程與QObjects QThread 繼承自QObject,它發(fā)射信號以指示線程執(zhí)行開始與結(jié)束,而且也提供了許多slots。更有趣的是,QObjects可以用于多線程,這是因為每個線 程被允許有它自己的事件循環(huán)。 QObject 可重入性 QObject是可重入的。它的大多數(shù)非GUI子類,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp, QProcess也是可重入的,在多個線程中同時使用這些類是可能的。需要注意的是,這些類被設(shè)計成在一個單線程中創(chuàng)建與使用,因此,在一個線程中創(chuàng)建一 個對象,而在另外的線程中調(diào)用它的函數(shù),這樣的行為不能保證工作良好。有三種約束需要注意: 1,QObject的孩子總是應(yīng)該在它父親被創(chuàng)建的那個線程中創(chuàng)建。這意味著,你絕不應(yīng)該傳遞QThread對象作為另一個對象的父親(因為 QThread對象本身會在另一個線程中被創(chuàng)建) 2,事件驅(qū)動對象僅僅在單線程中使用。明確地說,這個規(guī)則適用于"定時器機制“與”網(wǎng)格模塊“,舉例來講,你不應(yīng)該在一個線程中開始一個定時器或是連接一 個套接字,當(dāng)這個線程不是這些對象所在的線程。 3,你必須保證在線程中創(chuàng)建的所有對象在你刪除QThread前被刪除。這很容易做到:你可以run()函數(shù)運行的棧上創(chuàng)建對象。 盡管QObject是可重入的,但GUI類,特別是QWidget與它的所有子類都是不可重入的。它們僅用于主線程。正如前面提到過的, QCoreApplication::exec()也必須從那個線程中被調(diào)用。實踐上,不會在別的線程中使用GUI類,它們工作在主線程上,把一些耗時的 操作放入獨立的工作線程中,當(dāng)工作線程運行完成,把結(jié)果在主線程所擁有的屏幕上顯示。 逐線程事件循環(huán) 每個線程可以有它的事件循環(huán),初始線程開始它的事件循環(huán)需使用QCoreApplication::exec(),別的線程開始它的事件循環(huán)需要用 QThread::exec().像QCoreApplication一樣,QThreadr提供了exit(int)函數(shù),一個quit() slot。 線程中的事件循環(huán),使得線程可以使用那些需要事件循環(huán)的非GUI 類(如,QTimer,QTcpSocket,QProcess)。也可以把任何線程的signals連接到特定線程的slots,也就是說信號-槽機制 是可以跨線程使用的。對于在QApplication之前創(chuàng)建的對象,QObject::thread()返回0,這意味著主線程僅為這些對象處理投遞事 件,不會為沒有所屬線程的對象處理另外的事件??梢杂肣Object::moveToThread()來改變它和它孩子們的線程親緣關(guān)系,假如對象有父 親,它不能移動這種關(guān)系。在另一個線程(而不是創(chuàng)建它的那個線程)中delete QObject對象是不安全的。除非你可以保證在同一時刻對象不在處理事件??梢杂肣Object::deleteLater(),它會投遞一個 DeferredDelete事件,這會被對象線程的事件循環(huán)最終選取到。 假如沒有事件循環(huán)運行,事件不會分發(fā)給對象。舉例來說,假如你在一個線程中創(chuàng)建了一個QTimer對象,但從沒有調(diào)用過exec(),那么QTimer就 不會發(fā)射它的timeout()信號.對deleteLater()也不會工作。(這同樣適用于主線程)。你可以手工使用線程安全的函數(shù) QCoreApplication::postEvent(),在任何時候,給任何線程中的任何對象投遞一個事件,事件會在那個創(chuàng)建了對象的線程中通過事 件循環(huán)派發(fā)。事件過濾器在所有線程中也被支持,不過它限定被監(jiān)視對象與監(jiān)視對象生存在同一線程中。類似地,QCoreApplication:: sendEvent(不是postEvent()),僅用于在調(diào)用此函數(shù)的線程中向目標(biāo)對象投遞事件。 從別的線程中訪問QObject子類 QObject和所有它的子類是非線程安全的。這包括整個的事件投遞系統(tǒng)。需要牢記的是,當(dāng)你正從別的線程中訪問對象時,事件循環(huán)可以向你的 QObject子類投遞事件。假如你調(diào)用一個不生存在當(dāng)前線程中的QObject子類的函數(shù)時,你必須用mutex來保護QObject子類的內(nèi)部數(shù)據(jù), 否則會遭遇災(zāi)難或非預(yù)期結(jié)果。像其它的對象一樣,QThread對象生存在創(chuàng)建它的那個線程中---不是當(dāng)QThread::run()被調(diào)用時創(chuàng)建的那 個線程。一般來講,在你的QThread子類中提供slots是不安全的,除非你用mutex保護了你的成員變量。 另一方面,你可以安全的從QThread::run()的實現(xiàn)中發(fā)射信號,因為信號發(fā)射是線程安全的。 跨線程的信號-槽 Qt支持三種類型的信號-槽連接: 1,直接連接,當(dāng)signal發(fā)射時,slot立即調(diào)用。此slot在發(fā)射signal的那個線程中被執(zhí)行(不一定是接收對象生存的那個線程) 2,隊列連接,當(dāng)控制權(quán)回到對象屬于的那個線程的事件循環(huán)時,slot被調(diào)用。此slot在接收對象生存的那個線程中被執(zhí)行 3,自動連接(缺省),假如信號發(fā)射與接收者在同一個線程中,其行為如直接連接,否則,其行為如隊列連接。 連接類型可能通過以向connect()傳遞參數(shù)來指定。注意的是,當(dāng)發(fā)送者與接收者生存在不同的線程中,而事件循環(huán)正運行于接收者的線程中,使用直接連 接是不安全的。同樣的道理,調(diào)用生存在不同的線程中的對象的函數(shù)也是不是安全的。QObject::connect()本身是線程安全的。 多線程與隱含共享 Qt為它的許多值類型使用了所謂的隱含共享(implicitsharing)來優(yōu)化性能。原理比較簡單,共享類包含一個指向共享數(shù)據(jù)塊的指針,這個數(shù)據(jù) 塊中包含了真正原數(shù)據(jù)與一個引用計數(shù)。把深拷貝轉(zhuǎn)化為一個淺拷貝,從而提高了性能。這種機制在幕后發(fā)生作用,程序員不需要關(guān)心它。如果深入點看,假如對象 需要對數(shù)據(jù)進行修改,而引用計數(shù)大于1,那么它應(yīng)該先detach()。以使得它修改不會對別的共享者產(chǎn)生影響,既然修改后的數(shù)據(jù)與原來的那份數(shù)據(jù)不同 了,因此不可能再共享了,于是它先執(zhí)行深拷貝,把數(shù)據(jù)取回來,再在這份數(shù)據(jù)上進行修改。例如: void QPen::setStyle(Qt::PenStyle style) { detach(); // detach From common data d->style = style; // set the style member
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
一般認(rèn)為,隱含共享與多線程不太和諧,因為有引用計數(shù)的存在。對引用計數(shù)進行保護的方法之一是使用mutex,但它很慢,Qt早期版本沒有提供一個滿意的 解決方案。從4.0開始,隱含共享類可以安全地跨線程拷貝,如同別的值類型一樣。它們是完全可重入的。隱含共享真的是"implicit"。它使用匯編語 言實現(xiàn)了原子性引用計數(shù)操作,這比用mutex快多了。
假如你在多個線程中同進訪問相同對象,你也需要用mutex來串行化訪問順序,就如同其他可重入對象那樣。總的來講,隱含共享真的給”隱含“掉了,在多線程程序中,你可以把它們看成是一般的,非共享的,可重入的類型,這種做法是安全的。
十二.服務(wù)端
服務(wù)端還使用了兩個socket,一個用于和服務(wù)端口綁定后偵聽是否有服務(wù)請求,另外一個用于發(fā)送圖像數(shù)據(jù)
十三.攝像頭采集到的數(shù)據(jù)的格式可以為下面格式(include/linux/videodev.h),本程序使用的是VIDEO_PALETTE_RGB32(在v4l_set_picture( v4l_device *vd )中定義)
#define VIDEO_PALETTE_GREY 1 /* Linear greyscale */
#define VIDEO_PALETTE_HI240 2 /* High 240 cube (BT848) */
#define VIDEO_PALETTE_RGB565 3 /* 565 16 bit RGB */
#define VIDEO_PALETTE_RGB24 4 /* 24bit RGB */
#define VIDEO_PALETTE_RGB32 5 /* 32bit RGB */
#define VIDEO_PALETTE_RGB555 6 /* 555 15bit RGB */
#define VIDEO_PALETTE_YUV422 7 /* YUV422 capture */
#define VIDEO_PALETTE_YUYV 8
#define VIDEO_PALETTE_UYVY 9 /* The great thing about standards is ... */
#define VIDEO_PALETTE_YUV420 10
#define VIDEO_PALETTE_YUV411 11 /* YUV411 capture */
#define VIDEO_PALETTE_RAW 12 /* RAW capture (BT848) */
#define VIDEO_PALETTE_YUV422P 13 /* YUV 4:2:2 Planar */
#define VIDEO_PALETTE_YUV411P 14 /* YUV 4:1:1 Planar */
#define VIDEO_PALETTE_YUV420P 15 /* YUV 4:2:0 Planar */
#define VIDEO_PALETTE_YUV410P 16 /* YUV 4:1:0 Planar */
#define VIDEO_PALETTE_PLANAR 13 /* start of planar entries */
#define VIDEO_PALETTE_COMPONENT 7 /* start of component entries */
int v4l_set_picture( v4l_device *vd )
{
//vd->picture.palette=VIDEO_PALETTE_YUV420P;
vd->picture.palette=VIDEO_PALETTE_RGB32;
if( ioctl( vd->fd, VIDIOCSPICT, &( vd->picture ) ) < 0 )
{
return -1;
}
return 0;
}