本文档是自己所整理的一份文档,部分是原创,还转贴了网上的一此资料(已经标明了),(难点是多线程的编写),是有源代码的,大家可以作为参考,用到的知识是视频采集,压缩解压(xvid),实时传输(jrtp),基于qt库所写的,由于本人对qt下的多线程还不很了解,只做了单线程的(采集-->压缩-->解压-->发送-->接收--显示),用timer来刷新视频播放窗口,现在正在研究多线程(代码还在整理中),以后再换成多线程(用qt4的多线程,因为qt4的线程继承于QObject的,线程间可以使用signal-slot机制通信),建设先看看“linux下的tv播放器.doc(网上的资料)”
一.把视频显示到界面的方法
(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;
//该步很重要,设置标志
//让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的消息循环来处理的
2)QThread本身是继承于QObject的,为线程间的signal-slot机制打下了基础(Qt4),而qt3的线程不是继承于QObject,不能在线程间使用signal-slot机制(如QObject::connect(Thread, SIGNAL(Log(QString)), this, SLOT(Logslots(QString)))不能应用在qt3中,只能应用在qt4中)
3)QObject本身和线程是没关系的,提供signal-slot机制相关信息
三.事件和信号的区别(问题:哪什么时候用事件,什么时候用信号呢?是不是不同的线程间用事件,信号不能用在线程间?但现在的qt4可以在线程间发送信号)
仔细来看,事件与信号其实并无多大差别,从我们对其需求上来说,都只要能注册事件或信号响应函数,在事件或信号产生时能够被通知到即可。但有一项区别在于,事件处理函数的返回值是有意义的,我们要根据这个返回值来确定是否还要继续事件的处理,比如在QT中,事件处理函数如果返回true,则这个事件处理已完成,QApplication会接着处理下一个事件,而如果返回false,那么事件分派函数会继续向上寻找下一个可以处理该事件的注册方法。信号处理函数的返回值对信号分派器来说是无意义的。
另外还有一个需要我们关注的问题是事件和信号处理时的优先级问题。在QT中,事件因为都是与窗口相关的,所以事件回调时都是从当前窗口开始,一级一级向上派发,直到有一个窗口返回true,截断了事件的处理为止。对于信号的处理则比较简单,默认是没有顺序的,如果需要明确的顺序,可以在信号注册时显示地指明槽的位置。
在QT中,事件使用了一个事件队列来维护,如果事件的处理中又产生了新的事件,那么新的事件会加入到队列尾,直到当前事件处理完毕后, QApplication再去队列头取下一个事件来处理。而信号的处理方式有些不同,信号处理是立即回调的,也就是一个信号产生后,他上面所注册的所有槽都会立即被回调。这样就会产生一个递归调用的问题,比如某个信号处理器中又产生了一个信号,会使得信号的处理像一棵树一样的展开。
四.基于QT4的一个多线程工程实现(网上见到的一个比较好的例子,关于线程任务分配) (网址:http://easons.blogbus.com/logs/14845035.html)
想法:需要模仿ACE异步调用的方法,在一个线程分配任务给工作线程,并等待工作线程完成后返回结果。
定义一个线程类:
头文件:
#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
实现文件:
#include "mythread.h"
MyThread::MyThread()
{
}
MyThread::~MyThread()
{
}
bool MyThread::StartThread()
{
start();//线程启动,调用run
return true;
}
bool MyThread::StopThread()
{
exit();
return false;
}
void MyThread::run()
{
exec();//进入本线程的消息循环
}
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;
}
}
主函数:
#include <QtCore/QCoreApplication>
#include "mythread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread mythread;
mythread.StartThread();//启动辅助线程,
MethodEvent event;//新建一个任务
QCoreApplication::sendEvent(&mythread, &event);//发给工作线程,并阻塞等待
int result = event.i;//得到返回值。
return a.exec();
}
说明:如果不用到返回值,也就是只是简单分配任务的话,可以用postEvent的方法,也就是要new一个event,发给工作线程,工作线程处理完事件后会去删除你new的event,主线程就不管了。换句话说,postEvent马上返回,不管event是否被处理完,两个线程同时在工作。而上面是主线程要等待工作线程完成工作后才可以继续,阻塞在sendEvent上了
五.图像循环队列(摄像头的采集数据放到图像循环队列)
程序通过建立带共享锁的4帧图像循环队列做为图像采集线程和图像发送线程进行数据交换的公共缓冲区(带共享锁的循环队列在这个网址下有介绍:http://www.zaoxue.com/article/tech-55122.htm)
能够在通信中更好的对数据进行读写和存储,在程序编写过程中就把数据队列的方式改为了
循环队列。通过设定数据存储地址的头指针和尾指针,以及对数据存储长度状态值的判断,从而达到
循环队列的目的。当有新的数据到来时,数据的尾指针自动往后移,长度的也做相应的增加。同时判断数据的长度有没有超出BANK的地址范围,如果超出地址范围,则尾指针跳转到头指针之前,继续往后存储数据,形成了
循环队列;同样,当从RAM里取走数据时,数据的头指针自动往后移,长度的也做相应的增减。在队列的出队操作中,还要对数据的长度进行判断,如果达到了最大长度,则丢弃后面所有的数据。直到缓存区能有空间继续作队列为止。
六.视频采集数据缓冲机制的研究
在视频采集系统中,视频数据的采集与发送需要很好的性能,所以需要一个高性能的数据流缓冲机制,在传统的生产者/消费者模型的基础上,提出一种新的数据流缓冲模型(在一份叫做
视频
会议系统中数据缓冲机制的研究.pdf
中有介绍)(一个关于多线程同步的文章 :http://www.vckbase.com/document/viewdoc/?id=1080)
七.GNU/Linux中解决多线程互斥同步问题的分析和说明(
http://blog.chinaunix.net/u1/35100/showart.php?id=274716)
当解决多线程互斥同步的问题时,
经常会有如下几个问题:
1. 在一个给定的问题中, 需要多少个Mutex, 多少个Semaphore? 有什么规律?
2. 在对临界区加锁和等待信号量的顺序上有什么要求和规律?
3. 什么样操作适合放在临界区, 什么样的不适合?
下面就生产者和消费者问题来分析一些这几个问题.
下面是一个简单的实现程序:
生产者向数组sharedArray 中写入数据, 而消费者从该数组中读取数据.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /* 共享缓冲区的大小*/
int sharedArray[MAXSIZE]; /*sharedArray 是共享缓冲区*/
int curr=-1; /*curr 是用来指定sharedArray 当前存有数据的最大位置*/
/* 注意,sharedArray 和curr 都属于共享数据*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /* 锁定临界区的mutex*/
sem_t waitNonEmpty, waitNonFull; /* 等待" 非空资源" 和等待" 非满资源" 的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /* 是否有" 非空资源"*/
pthread_mutex_lock(&sharedMutex); /* 进入临界区*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonFull); /* 生成一个" 非满资源"*/
pthread_mutex_unlock(&sharedMutex); /* 离开临界区*/
sleep(2); /* 跟同步无关的费时操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /* 生成一个随机数据, 注意是10.0 而不是10*/
sem_wait(&waitNonFull); /* 是否有" 非满资源"*/
pthread_mutex_lock(&sharedMutex); /* 进入临界区*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /* 生成一个" 非空资源"*/
pthread_mutex_unlock(&sharedMutex); /* 离开临界区*/
sleep(1); /* 跟同步无关的费时操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /* 两个生产者和两个消费者*/
sem_init(&waitNonEmpty, 0, empty); /* 初始化信号量*/
sem_init(&waitNonFull, 0, full);
/* 注意, 本问题中的两种semaphore 是有一定关系的, 那就是它们的初始值之和应该等于共享缓冲区大小*/
/* 即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? 有什么规律?
在本问题中, 共需要一个Mutex 和两个Semaphore.
其中,Mutex 是用来锁定临界区的, 以解决对共享数据的互斥访问问题( 无论是对生成者还是对消费者);
我们共需要两个Semaphore, 这是因为在本问题中共有两个稀缺资源.
第一种是" 非空" 这种资源, 是在消费者之间进行竞争的.
第二种是" 非满" 这种资源, 是在生产者之间进行竞争的.
所以, 一般来说, 需要锁定临界区, 就需要Mutex; 有几种稀缺资源就需要几个Semaphore.
对稀缺资源的分析不能想当然. 稀缺资源不一定是指被共享的资源, 很多时候是指线程会被阻塞的条件( 除了要进临界区被阻塞外).
本例中, 消费者会在缓冲区为空时被阻塞, 所以" 非空" 是一种稀缺资源;
生产者会在缓冲区为满时被阻塞, 所以" 非满" 也是一种稀缺资源.
2. 在对临界区加锁和等待信号量的顺序上有什么要求和规律?
这里要说两点:
第一, 不要将等待信号量的语句放在被锁定的临界区内, 这样会造成死锁. 而且这也是很没有必要的.
比如, 消费者在缓冲区没有数据的时候进入临界区, 这样就会把临界区锁上, 由于没有数据, 消费者也会被锁上.
这时, 任何生产者都会由于临界区被锁上而被block 住, 这样就造成了死锁.
第二, 如果有多个Semaphore 需要等待, 那么每个线程中, 最好对这多个信号量进行等待的顺序一致,
不然的话很容易造成死锁.
3. 什么样操作适合放在临界区, 什么样的不适合?
一般来说, 临界区中只放对共享数据进行访问的语句, 这样会改善程序的性能.
很多时候, 取出共享数据的副本后, 对副本进行费时的各种操作就不需要放在临界区了.
比如, 本例中的sleep 语句就根本不需要放入临界区.
1. 在一个给定的问题中, 需要多少个Mutex, 多少个Semaphore? 有什么规律?
2. 在对临界区加锁和等待信号量的顺序上有什么要求和规律?
3. 什么样操作适合放在临界区, 什么样的不适合?
下面就生产者和消费者问题来分析一些这几个问题.
下面是一个简单的实现程序:
生产者向数组sharedArray 中写入数据, 而消费者从该数组中读取数据.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#define MAXSIZE 5 /* 共享缓冲区的大小*/
int sharedArray[MAXSIZE]; /*sharedArray 是共享缓冲区*/
int curr=-1; /*curr 是用来指定sharedArray 当前存有数据的最大位置*/
/* 注意,sharedArray 和curr 都属于共享数据*/
int empty=0;
int full=MAXSIZE;
pthread_mutex_t sharedMutex=PTHREAD_MUTEX_INITIALIZER; /* 锁定临界区的mutex*/
sem_t waitNonEmpty, waitNonFull; /* 等待" 非空资源" 和等待" 非满资源" 的semaphor*/
void * readData(void * whichone)
{
int data, position;
while (1){
sem_wait(&waitNonEmpty); /* 是否有" 非空资源"*/
pthread_mutex_lock(&sharedMutex); /* 进入临界区*/
data = sharedArray[curr];
position = curr--;
printf ("%s read from the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonFull); /* 生成一个" 非满资源"*/
pthread_mutex_unlock(&sharedMutex); /* 离开临界区*/
sleep(2); /* 跟同步无关的费时操作*/
}
}
void * writeData(void * whichone)
{
int data, position;
while (1) {
data=(int)(10.0*random()/RAND_MAX); /* 生成一个随机数据, 注意是10.0 而不是10*/
sem_wait(&waitNonFull); /* 是否有" 非满资源"*/
pthread_mutex_lock(&sharedMutex); /* 进入临界区*/
position = ++curr;
sharedArray[curr]=data;
printf ("%s wrote to the %dth: %d, \n", (char*)whichone, position, data);
sem_post(&waitNonEmpty); /* 生成一个" 非空资源"*/
pthread_mutex_unlock(&sharedMutex); /* 离开临界区*/
sleep(1); /* 跟同步无关的费时操作*/
}
}
int main (int argc, char** argv)
{
pthread_t consumer1, consumer2, producer1, producer2; /* 两个生产者和两个消费者*/
sem_init(&waitNonEmpty, 0, empty); /* 初始化信号量*/
sem_init(&waitNonFull, 0, full);
/* 注意, 本问题中的两种semaphore 是有一定关系的, 那就是它们的初始值之和应该等于共享缓冲区大小*/
/* 即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? 有什么规律?
在本问题中, 共需要一个Mutex 和两个Semaphore.
其中,Mutex 是用来锁定临界区的, 以解决对共享数据的互斥访问问题( 无论是对生成者还是对消费者);
我们共需要两个Semaphore, 这是因为在本问题中共有两个稀缺资源.
第一种是" 非空" 这种资源, 是在消费者之间进行竞争的.
第二种是" 非满" 这种资源, 是在生产者之间进行竞争的.
所以, 一般来说, 需要锁定临界区, 就需要Mutex; 有几种稀缺资源就需要几个Semaphore.
对稀缺资源的分析不能想当然. 稀缺资源不一定是指被共享的资源, 很多时候是指线程会被阻塞的条件( 除了要进临界区被阻塞外).
本例中, 消费者会在缓冲区为空时被阻塞, 所以" 非空" 是一种稀缺资源;
生产者会在缓冲区为满时被阻塞, 所以" 非满" 也是一种稀缺资源.
2. 在对临界区加锁和等待信号量的顺序上有什么要求和规律?
这里要说两点:
第一, 不要将等待信号量的语句放在被锁定的临界区内, 这样会造成死锁. 而且这也是很没有必要的.
比如, 消费者在缓冲区没有数据的时候进入临界区, 这样就会把临界区锁上, 由于没有数据, 消费者也会被锁上.
这时, 任何生产者都会由于临界区被锁上而被block 住, 这样就造成了死锁.
第二, 如果有多个Semaphore 需要等待, 那么每个线程中, 最好对这多个信号量进行等待的顺序一致,
不然的话很容易造成死锁.
3. 什么样操作适合放在临界区, 什么样的不适合?
一般来说, 临界区中只放对共享数据进行访问的语句, 这样会改善程序的性能.
很多时候, 取出共享数据的副本后, 对副本进行费时的各种操作就不需要放在临界区了.
比如, 本例中的sleep 语句就根本不需要放入临界区.
八.教你如何测试循环缓冲区(代码我是参考DivX播放器源代码-playa-0.3.3src.zip这个开源软件的)
这个缓冲区主要包括这两个文件:Queue.h,Queue.cpp(
具体代码请参考程序)
主要函数:
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;
}
测试自己所建的两个视频采集缓冲区:(
这个是应用在多线程系统中的,
但测试是用于一个线程中全部执行的,
测试是没有问题的,但如果把“从每二个视频缓冲区中取出视频数据--->把从第二个视频缓冲区中取出的视频数据显示出来”这部分放到解压线程中测试时是可以显示图像的,但显示的图像的颜色变了,我想应该是缓冲区的QMutex
问题)
//
第一种测试(
在同一个线程中执行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//
将视频数据放入到每一个缓冲区
parent->readCapBuff(testbuffer,320*240*4);//
从每一个缓冲区中取得视频数据
parent->writeEnCodebuff(testbuffer,320*240*4);//
把从第一个缓冲区中取得的视频数据放到第二个视频缓冲区中
parent->readEnCodebuff(testbuffer2,320*240*4);//
从每二个视频缓冲区中取出视频数据
//add--->>
把从第二个视频缓冲区中取出的视频数据显示出来
//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
}
}
//
第二种情况(
在两个线程中分别执行)
void CapThread::run(){
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*4);//
将视频数据放入到每一个缓冲区
parent->readCapBuff(testbuffer,320*240*4);//
从每一个缓冲区中取得视频数据
parent->writeEnCodebuff(testbuffer,320*240*4);//
把从第一个缓冲区中取得的视频数据放到第二个视频缓冲区中
}
}
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()
是否符合我们的要求,本人设计了一种简单的测试方法:
就是用ring_write()
把采集到的视频数据放到缓冲区中,然后用ring_read()
从缓冲区中读取数据,然后将数据保存成一张图片,如果图片是输入的图像就证明了缓冲区的代码是正确的,
由于我使用的队列缓冲区完全是由生产者驱动的,就是说队列的推进的速度等于生产者生产产品的速度,
消费者不一定必必须消费每一个产品,在一定程序上,丢失某些数据是允许的
循环缓冲区由一个固定大小的内存缓冲区构成,进程使用这个内存缓冲区进行日志记录。顾名思义,该缓冲区采用循环的方式进行实现。当该缓冲区填满了数据时,无需为新的数据分配更多的内存,而是从缓冲区开始的位置对其进行写操作,因此将覆盖以前的内容。
1)对循环缓冲区进行写操作
2)注意事项---在多线程程序中使用循环缓冲区
这个部分介绍了在多线程应用程序中使用循环缓冲区启时需要考虑的一些重要方面。
在访问一个公共的资源时,
同步 始终是多线程程序不可缺少的部分。因为每个线程都试图对全局空间进行写操作,所以必须确保它们同步地写入内存,否则消息就会遭到破坏。通常,每个线程在写入缓冲区之前都持有一个锁,在完成操作时释放该锁。您可以下载一个使用锁对内存进行写操作的循环缓冲区示例。
这种方法具有以下的缺点:如果您的应用程序中包含几个线程,并且每个线程都在进行访问缓冲区,那么该进程的整体性能将会受到影响,因为这些线程将在获得和释放锁上花费了大部分的时间。
通过使得每个线程将数据写入到它自己的内存块,就可以完全避免同步问题。当收到来自用户的转储数据的请求时,每个线程获得一个锁,并将其转储到中心 位置。因为仅在将数据刷新到磁盘时获得锁,所以性能并不会受到很大的影响。
九.每二种缓冲方法,先建多个buffer,然后将这些buffer构成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下如何访问一个共享对象呢?
这是一个我不断思考的问题:有一个共享的视频采集缓冲区(采集线程负责把视频数据放入缓冲区,压缩线程负责从缓冲区中取出视频数据再压缩),到底这个全局的视频采集缓冲区如何可以使采集线程和压缩线程共享呢,开始时我是把这个视频采集缓冲区对象放到采集线程中的,这样采集线程就能访问到这个视频采集缓冲区,但是压缩线程如何共享这个视频采集缓冲区呢?(由于这个压缩线程要读这个视频缓冲区要用到视频缓冲对象的一个读函数,但这个视频缓冲对象是在采集线程中定义的,压缩线程是不能访问采集线程下的子对象(视频采集缓冲区)这成了一个问题),经过两天的思考终于找到了一种办法(由于qt跟vc的编程方法有很大不同,加上网上的资料比较少),我决定把视频缓冲区对象放到主线程(界面对象)中,然后在采集线程和压缩线程中加上一个主线程对像(通过这个对象就能方便地使采集线程和压缩线程访问共享这个视频采集缓冲区).代码如下
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();//新建一个视频采集缓冲区
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();//启动采集线程
};
//主线程中对视频采集缓冲区的操作
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对应)
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);
}
//采集线程执行的任务 (把采集到的数据pBuffer放到视频采集缓冲区中)
void CapThread::run()
{
for(;;){
v4l_grab_movie(&v4l_dev);
unsigned char *pBuffer= v4l_dev.buffer;
parent->writeCapBuff(pBuffer,320*240*3);(这样就可以访问到共享的视频采集缓冲区了)
}
}
//压缩线程
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);
}
//压缩线程要执行的任务(从视频缓冲区中取出数据并压缩)
void CXvidEnc::run()
{
for(;;){
parent->readCapBuff(getCapBuff, 320*240*4);
Encode(getCapBuff);
}
}
Qt 中的多线程(一)
QT通过三种形式提供了对线程的支持。它们分别是,一、平台无关的线程类,二、线程安全的事件投递,三、跨线程的信号-槽连接。这使得开发轻巧的多 线程Qt程序更为容易,并能充分利用多处理器机器的优势。多线程编程也是一个有用的模式,它用于解决执行较长时间的操作而不至于用户界面失去响应。在Qt 的早期版本中,在构建库时有不选择线程支持的选项,从4.0开始,线程总是有效的。
线程类
Qt 包含下面一些线程相关的类:
QThread 提供了开始一个新线程的方法
QThreadStorage 提供逐线程数据存储
QMutex 提供相互排斥的锁,或互斥量
QMutexLocker 是一个便利类,它可以自动对QMutex加锁与解锁
QReadWriterLock 提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker 是便利类,它自动对QReadWriteLock加锁与解锁
QSemaphore 提供了一个整型信号量,是互斥量的泛化
QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
QThread 提供了开始一个新线程的方法
QThreadStorage 提供逐线程数据存储
QMutex 提供相互排斥的锁,或互斥量
QMutexLocker 是一个便利类,它可以自动对QMutex加锁与解锁
QReadWriterLock 提供了一个可以同时读操作的锁
QReadLocker与QWriteLocker 是便利类,它自动对QReadWriteLock加锁与解锁
QSemaphore 提供了一个整型信号量,是互斥量的泛化
QWaitCondition 提供了一种方法,使得线程可以在被另外线程唤醒之前一直休眠。
创建一个线程
为创建一个线程,子类化QThread并且重写它的run()函数,例如:
class MyThread : public QThread
{
Q_OBJECT protected:
void run();
};
void MyThread::run()
{
...
}
之后,创建这个线程对象的实例,调用QThread::start()。于是,在run()里出现的代码将会在另外线程中被执行。
注意:QCoreApplication::exec()必须总是在主线程(执行main()的那个线程)中被调用,不能从一个QThread中调用。在 GUI程序中,主线程也被称为GUI线程,因为它是唯一一个允许执行GUI相关操作的线程。另外,你必须在创建一个QThread之前创建 QApplication(orQCoreApplication)对象。
线程同步
class MyThread : public QThread
{
Q_OBJECT protected:
void run();
};
void MyThread::run()
{
...
}
之后,创建这个线程对象的实例,调用QThread::start()。于是,在run()里出现的代码将会在另外线程中被执行。
注意:QCoreApplication::exec()必须总是在主线程(执行main()的那个线程)中被调用,不能从一个QThread中调用。在 GUI程序中,主线程也被称为GUI线程,因为它是唯一一个允许执行GUI相关操作的线程。另外,你必须在创建一个QThread之前创建 QApplication(orQCoreApplication)对象。
线程同步
QMutex, QReadWriteLock, QSemaphore, QWaitCondition 提供了线程同步的手段。使用线程的主要想法是希望它们可以尽可能并发执行,而一些关键点上线程之间需要停止或等待。例如,假如两个线程试图同时访问同一个 全局变量,结果可能不如所愿。
QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。Mutexes常用来保护共享数据访问。
QReadWriterLock 与QMutex相似,除了它对 "read","write"访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
QMutex 提供相互排斥的锁,或互斥量。在一个时刻至多一个线程拥有mutex,假如一个线程试图访问已经被锁定的mutex,那么它将休眠,直到拥有mutex的线程对此mutex解锁。Mutexes常用来保护共享数据访问。
QReadWriterLock 与QMutex相似,除了它对 "read","write"访问进行区别对待。它使得多个读者可以共时访问数据。使用QReadWriteLock而不是QMutex,可以使得多线程程序更具有并发性。
QReadWriteLock lock;
void ReaderThread::run()
{
// ...
lock.lockForRead();
read_file();
lock.unlock();
//...
}
void WriterThread::run()
{
// ...
lock.lockForWrite();
write_file();
lock.unlock();
// ...
}
void ReaderThread::run()
{
// ...
lock.lockForRead();
read_file();
lock.unlock();
//...
}
void WriterThread::run()
{
// ...
lock.lockForWrite();
write_file();
lock.unlock();
// ...
}
QSemaphore 是QMutex的一般化,它可以保护一定数量的相同资源,与此相对,一个mutex只保护一个资源。下面例子中,使用QSemaphore来控制对环状缓 冲的访问,此缓冲区被生产者线程和消费者线程共享。生产者不断向缓冲写入数据直到缓冲末端,再从头开始。消费者从缓冲不断读取数据。信号量比互斥量有更好 的并发性,假如我们用互斥量来控制对缓冲的访问,那么生产者,消费者不能同时访问缓冲。然而,我们知道在同一时刻,不同线程访问缓冲的不同部分并没有什么 危害。
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 允许线程在某些情况发生时唤醒另外的线程。一个或多个线程可以阻塞等待一QWaitCondition ,用wakeOne()或wakeAll()设置一个条件。wakeOne()随机唤醒一个,wakeAll()唤醒所有。
下面的例子中,生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),如果是,线程停下来等待 bufferNotFull条件。如果不是,在缓冲中生产数据,增加numUsedBytes,激活条件bufferNotEmpty。使用mutex来 保护对numUsedBytes的访问。另外,QWaitCondition::wait()接收一个mutex作为参数,这个mutex应该被调用线程 初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会处于锁定状态,而且,从锁定状态到等待状态的转换是原子 操作,这阻止了竞争条件的产生。当程序开始运行时,只有生产者可以工作。消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个 字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。
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文档中,术语“可重入”与“线程安全”被用来说明一个函数如何用于多线程程序。假如一个类的任何函数在此类的多个不同的实例上,可以被多个线程同时 调用,那么这个类被称为是“可重入”的。假如不同的线程作用在同一个实例上仍可以正常工作,那么称之为“线程安全”的。 大多数c++类天生就是可重入的,因为它们典型地仅仅引用成员数据。任何线程可以在类的一个实例上调用这样的成员函数,只要没有别的线程在同一个实例上调 用这个成员函数。举例来讲,下面的Counter 类是可重入的:
class Counter
{
public:
Counter() {n=0;}
void increment() {++n;}
void decrement() {--n;}
int value() const {return n;}
private:
int n;
};
这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c++中的++和--操作符不是原子操作。实际上,它们会被扩展为三个机器指令: 1,把变量值装入寄存器 2,增加或减少寄存器中的值 3,把寄存器中的值写回内存 假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该串行化:A执行123步骤时不应 被打断。使这个类成为线程安全的最简单方法是使用QMutex来保护数据成员:
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类在构造函数中自动对mutex进行加锁,在析构函数中进行解锁。随便一提的是,mutex使用了mutable关键字来修饰, 因为我们在value()函数中对mutex进行加锁与解锁操作,而value()是一个const函数。 大多数Qt类是可重入,非线程安全的。有一些类与函数是线程安全的,它们主要是线程相关的类,如QMutex,QCoreApplication:: postEvent()。 线程与QObjects QThread 继承自QObject,它发射信号以指示线程执行开始与结束,而且也提供了许多slots。更有趣的是,QObjects可以用于多线程,这是因为每个线 程被允许有它自己的事件循环。 QObject 可重入性 QObject是可重入的。它的大多数非GUI子类,像QTimer,QTcpSocket,QUdpSocket,QHttp,QFtp, QProcess也是可重入的,在多个线程中同时使用这些类是可能的。需要注意的是,这些类被设计成在一个单线程中创建与使用,因此,在一个线程中创建一 个对象,而在另外的线程中调用它的函数,这样的行为不能保证工作良好。有三种约束需要注意: 1,QObject的孩子总是应该在它父亲被创建的那个线程中创建。这意味着,你绝不应该传递QThread对象作为另一个对象的父亲(因为 QThread对象本身会在另一个线程中被创建) 2,事件驱动对象仅仅在单线程中使用。明确地说,这个规则适用于"定时器机制“与”网格模块“,举例来讲,你不应该在一个线程中开始一个定时器或是连接一 个套接字,当这个线程不是这些对象所在的线程。 3,你必须保证在线程中创建的所有对象在你删除QThread前被删除。这很容易做到:你可以run()函数运行的栈上创建对象。 尽管QObject是可重入的,但GUI类,特别是QWidget与它的所有子类都是不可重入的。它们仅用于主线程。正如前面提到过的, QCoreApplication::exec()也必须从那个线程中被调用。实践上,不会在别的线程中使用GUI类,它们工作在主线程上,把一些耗时的 操作放入独立的工作线程中,当工作线程运行完成,把结果在主线程所拥有的屏幕上显示。 逐线程事件循环 每个线程可以有它的事件循环,初始线程开始它的事件循环需使用QCoreApplication::exec(),别的线程开始它的事件循环需要用 QThread::exec().像QCoreApplication一样,QThreadr提供了exit(int)函数,一个quit() slot。 线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI 类(如,QTimer,QTcpSocket,QProcess)。也可以把任何线程的signals连接到特定线程的slots,也就是说信号-槽机制 是可以跨线程使用的。对于在QApplication之前创建的对象,QObject::thread()返回0,这意味着主线程仅为这些对象处理投递事 件,不会为没有所属线程的对象处理另外的事件。可以用QObject::moveToThread()来改变它和它孩子们的线程亲缘关系,假如对象有父 亲,它不能移动这种关系。在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用QObject::deleteLater(),它会投递一个 DeferredDelete事件,这会被对象线程的事件循环最终选取到。 假如没有事件循环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就 不会发射它的timeout()信号.对deleteLater()也不会工作。(这同样适用于主线程)。你可以手工使用线程安全的函数 QCoreApplication::postEvent(),在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事 件循环派发。事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。类似地,QCoreApplication:: sendEvent(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。 从别的线程中访问QObject子类 QObject和所有它的子类是非线程安全的。这包括整个的事件投递系统。需要牢记的是,当你正从别的线程中访问对象时,事件循环可以向你的 QObject子类投递事件。假如你调用一个不生存在当前线程中的QObject子类的函数时,你必须用mutex来保护QObject子类的内部数据, 否则会遭遇灾难或非预期结果。像其它的对象一样,QThread对象生存在创建它的那个线程中---不是当QThread::run()被调用时创建的那 个线程。一般来讲,在你的QThread子类中提供slots是不安全的,除非你用mutex保护了你的成员变量。 另一方面,你可以安全的从QThread::run()的实现中发射信号,因为信号发射是线程安全的。 跨线程的信号-槽 Qt支持三种类型的信号-槽连接: 1,直接连接,当signal发射时,slot立即调用。此slot在发射signal的那个线程中被执行(不一定是接收对象生存的那个线程) 2,队列连接,当控制权回到对象属于的那个线程的事件循环时,slot被调用。此slot在接收对象生存的那个线程中被执行 3,自动连接(缺省),假如信号发射与接收者在同一个线程中,其行为如直接连接,否则,其行为如队列连接。 连接类型可能通过以向connect()传递参数来指定。注意的是,当发送者与接收者生存在不同的线程中,而事件循环正运行于接收者的线程中,使用直接连 接是不安全的。同样的道理,调用生存在不同的线程中的对象的函数也是不是安全的。QObject::connect()本身是线程安全的。 多线程与隐含共享 Qt为它的许多值类型使用了所谓的隐含共享(implicitsharing)来优化性能。原理比较简单,共享类包含一个指向共享数据块的指针,这个数据 块中包含了真正原数据与一个引用计数。把深拷贝转化为一个浅拷贝,从而提高了性能。这种机制在幕后发生作用,程序员不需要关心它。如果深入点看,假如对象 需要对数据进行修改,而引用计数大于1,那么它应该先detach()。以使得它修改不会对别的共享者产生影响,既然修改后的数据与原来的那份数据不同 了,因此不可能再共享了,于是它先执行深拷贝,把数据取回来,再在这份数据上进行修改。例如: 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
}
}
一般认为,隐含共享与多线程不太和谐,因为有引用计数的存在。对引用计数进行保护的方法之一是使用mutex,但它很慢,Qt早期版本没有提供一个满意的 解决方案。从4.0开始,隐含共享类可以安全地跨线程拷贝,如同别的值类型一样。它们是完全可重入的。隐含共享真的是"implicit"。它使用汇编语 言实现了原子性引用计数操作,这比用mutex快多了。
假如你在多个线程中同进访问相同对象,你也需要用mutex来串行化访问顺序,就如同其他可重入对象那样。总的来讲,隐含共享真的给”隐含“掉了,在多线程程序中,你可以把它们看成是一般的,非共享的,可重入的类型,这种做法是安全的。
}
void QPen::detach()
{
if (d->ref != 1) {
... // perform a deep copy
}
}
一般认为,隐含共享与多线程不太和谐,因为有引用计数的存在。对引用计数进行保护的方法之一是使用mutex,但它很慢,Qt早期版本没有提供一个满意的 解决方案。从4.0开始,隐含共享类可以安全地跨线程拷贝,如同别的值类型一样。它们是完全可重入的。隐含共享真的是"implicit"。它使用汇编语 言实现了原子性引用计数操作,这比用mutex快多了。
假如你在多个线程中同进访问相同对象,你也需要用mutex来串行化访问顺序,就如同其他可重入对象那样。总的来讲,隐含共享真的给”隐含“掉了,在多线程程序中,你可以把它们看成是一般的,非共享的,可重入的类型,这种做法是安全的。
十二.服务端
服务端还使用了两个socket,一个用于和服务端口绑定后侦听是否有服务请求,另外一个用于发送图像数据
十三.摄像头采集到的数据的格式可以为下面格式(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;
}