sem.available() == 9, returns false
生产者线程写数据到buffer直到缓冲末端,然后重新从buffer的头部开始写。
A crude way to solve this problem is to have the producer fill the buffer, then wait until the consumer has read the entire buffer, and so on.
显然这样做效率是比较低的。
1
const
int
DataSize
=
100000
;
2
const
int
BufferSize
=
8192
;
3
char
buffer[BufferSize];
5
//
When the application starts, the reader thread will start
//
acquiring "free" bytes and convert them into "used" bytes
6
QSemaphore freeBytes(BufferSize);
//
producer线程在此区域写入数据
,初始资源数量为BufferSize
7
QSemaphore usedBytes;
//
consumer线程读取此区域的数据,初始资源数量为0
10
//
For this example, each byte counts as one resource.
11
//
In a real-world application, we would probably operate on larger
//
units (for example, 64 or 256 bytes at a time)
12
class
Producer :
public
QThread
13
{
14
public
:
15
void
run();
16
};
17
//生产者每acquire一次就,使用掉Buffer个资源中的一个,而写入的字符存入到buffer数组中
//从而消费者可用读取字符,从而消费者获取一个资源
18
void
Producer::run()
19
{
20
//qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
21
for
(
int
i
=
0
; i
<
DataSize;
++
i) {
22
freeBytes.acquire();
23
buffer[i
%
BufferSize]
=
"
ACGT
"
[(
int
)qrand()
%
4
];
24
usedBytes.release();
25
}
26
}
27
28
class
Consumer :
public
QThread
29
{
30
public
:
31
void
run();
32
};
33
34
void
Consumer::run()
35
{
36
for
(
int
i
=
0
; i
<
DataSize;
++
i) {
37
usedBytes.acquire();
38
fprintf(stderr,
"
%c
"
, buffer[i
%
BufferSize]);
39
freeBytes.release();
40
}
41
fprintf(stderr,
"
\n
"
);
42
}
43
//
Finally, in main(), we start the producer and consumer threads.
//
What happens then is that the producer converts some "free" space
//
into "used" space, and the consumer can then convert it back to
//
"free" space.
46
int
main(
int
argc,
char
*
argv[])
47
{
48
QCoreApplication app(argc, argv);
49
Producer producer;
50
Consumer consumer;
51
producer.start();
52
consumer.start();
53
producer.wait();
54
consumer.wait();
55
return
0
;
56
}
producer的run函数:
当producer线程执行run函数,如果buffer中已经满了,而没有consumer线程没有读,这样producer就不能再往buffer
中写字符。此时在
freeBytes.acquire处就阻塞直到consumer线程读(consume)数据。一旦producer获取到一个字节(资源)
就写如一个随机的字符,并调用
usedBytes.release从而consumer线程获取一个资源可以读一个字节的数据了。
consumer的run函数:
当consumer线程执行run函数,如果buffer中没有数据,就是资源=0,则consumer线程在此处阻塞。直到producer线程执行
写操作,写入一个字节,并执行
usedBytes.release从而使得consumer线程的可用资源数=1。则consumer线程从阻塞状态中退出,
并将
usedBytes资源数-1,当前资源数=0。
6.QWaitCondition
QWaitCondition ()
QWaitCondition key_pressed;
for
(;;) {
key_pressed.wait();
//
这是一个QWaitCondition全局变量
//
键被按下,做一些有趣的事
do_something();
}
或是这样:
forever {
mutex.
lock
();
keyPressed.wait(
&
mutex);
do_something();
mutex.unlock();
}
第四个线程回去读键按下并且每当它接收到一个的时候唤醒其它三个线程,就像这样:
QWaitCondition key_pressed;
for
(;;) {
getchar();
//
在key_pressed中导致引起任何一个线程。wait()将会从这个方法中返回并继续执行
key_pressed.wakeAll();
}
注意这三个线程被唤醒的顺序是未定义的,并且当键被按下时,
这些线程中的一个或多个还在do_something(),它们将不会被唤醒(因为它们现在没有等待条件变量)并且这个任务也就不会针对这次按键执行操作。这种情况是可以避免得,比如,就像下面这样做:
1
QMutex mymutex;
2
QWaitCondition key_pressed;
3
int
mycount
=
0
;
4
5
//
Worker线程代码
6
for
(;;) {
7
key_pressed.wait();
//
这是一个QWaitCondition全局变量
//keyPressed.wait(&mutex);
8
mymutex.
lock
();
9
mycount
++
;
10
mymutex.unlock();
11
do_something();
12
mymutex.
lock
();
13
mycount
--
;
14
mymutex.unlock();
15
}
16
17
//
读取按键线程代码
18
for
(;;) {
19
getchar();
20
mymutex.
lock
();
21
//
睡眠,直到没有忙碌的工作线程才醒来。
count==0说明没有Worker线程在do something
22
while
( count
>
0
) {
23
mymutex.unlock();
24
sleep(
1
);
25
mymutex.
lock
();
26
}
27
mymutex.unlock();
28
key_pressed.wakeAll();
29
}
应用条件变量对前面用信号量进行保护的环状缓冲区的例子进行改进:
下面的例子中:
1)生产者首先必须检查缓冲是否已满(numUsedBytes==BufferSize),如果是,线程停下来等待bufferNotFull条件。如果不是,在缓冲中生产数据,增加numUsedBytes,激活条件 bufferNotEmpty。
2)使用mutex来保护对numUsedBytes的访问。
另外,QWaitCondition::
wait()接收一个mutex作为参数,这个mutex应该被调用线程初始化为锁定状态。在线程进入休眠状态之前,mutex会被解锁。而当线程被唤醒时,mutex会再次处于锁定状态。
而且,从锁定状态到等待状态的转换是原子操作,这阻止了竞争条件的产生。当程序开始运行时,只有生产者可以工作。消费者被阻塞等待bufferNotEmpty条件,一旦生产者在缓冲中放入一个字节,bufferNotEmpty条件被激发,消费者线程于是被唤醒。
1
const
int
DataSize
=
100000
;
2
const
int
BufferSize
=
8192
;
3
char
buffer[BufferSize];
4
5
QWaitCondition bufferNotEmpty;
6
QWaitCondition bufferNotFull;
7
QMutex mutex;
8
int
numUsedBytes
=
0
;
9
10
class
Producer :
public
QThread
11
{
12
public
:
13
void
run();
14
};
15
16
void
Producer::run()
17
{
18
qsrand(QTime(
0
,
0
,
0
).secsTo(QTime::currentTime()));
19
20
for
(
int
i
=
0
; i
<
DataSize;
++
i) {
21
mutex.
lock
();
//producer线程首先检查缓冲区是否已满
22
if
(numUsedBytes
==
BufferSize)//缓冲区已满,等待consumer来减少numUsedBytes
// bufferNotFull.wait(&mutex)先调用
mutex.unlock()然后收到信号时调用
mutex.
lock
()
23
bufferNotFull.wait(
&
mutex);//缓冲区已满等待bufferNotFull的条件变量成立变为有信号
24
mutex.unlock();
25
26
buffer[i
%
BufferSize]
=
"
ACGT
"
[(
int
)qrand()
%
4
];
27
28
mutex.
lock
();
29
++
numUsedBytes; //producer用掉一个Bytes,表示producer写入buffer中的字节数
30
bufferNotEmpty.wakeAll();
31
mutex.unlock();
32
}
33
}
35
class
Consumer :
public
QThread
36
{
37
public
:
38
void
run();
39
};
40
41
void
Consumer::run()
42
{
43
for
(
int
i
=
0
; i
<
DataSize;
++
i) {
44
mutex.
lock
();
45
if
(numUsedBytes
==
0
)
46
bufferNotEmpty.wait(
&
mutex);
47
mutex.unlock();
48
49
fprintf(stderr,
"
%c
"
, buffer[i
%
BufferSize]);
50
51
mutex.
lock
();
52
--
numUsedBytes;
53
bufferNotFull.wakeAll();
54
mutex.unlock();
55
}
56
fprintf(stderr,
"
\n
"
);
57
}
58
59
int
main(
int
argc,
char
*
argv[])
60
{
61
QCoreApplication app(argc, argv);
62
Producer producer;
63
Consumer consumer;
64
producer.start();
65
consumer.start();
66
producer.wait();
67
consumer.wait();
68
return
0
;
69
}
另外一个例子:
1
#include
<qapplication.h>
2
#include
<qpushbutton.h>
3
4
//
全局条件变量
5
QWaitCondition mycond;
6
7
//
Worker类实现
8
class
Worker :
public
QPushButton,
public
QThread
9
{
10
Q_OBJECT
11
12
public
:
13
Worker(QWidget
*
parent
=
0
,
const
char
*
name
=
0
)
14
: QPushButton(parent, name)
15
{
16
setText(
"
Start Working
"
);
17
18
//
连接从QPushButton继承来的信号和我们的slotClicked()方法
19
connect(
this
, SIGNAL(clicked()), SLOT(slotClicked()));
20
21
//
调用从QThread继承来的start()方法……这将立即开始线程的执行
22
QThread::start();
23
}
24
25
public
slots:
26
void
slotClicked()
27
{
28
//
唤醒等待这个条件变量的一个线程
29
mycond.wakeOne();
30
}
31
32
protected
:
33
void
run()
34
{
35
//
这个方法将被新创建的线程调用……
36
37
while
( TRUE ) {
38
//
锁定应用程序互斥锁,并且设置窗口标题来表明我们正在等待开始工作
39
qApp
->
lock
();
40
setCaption(
"
Waiting
"
);
41
qApp
->
unlock();
42
43
//
等待直到我们被告知可以继续
44
mycond.wait();
45
46
//
如果我们到了这里,我们已经被另一个线程唤醒……让我们来设置标题来表明我们正在工作
47
qApp
->
lock
();
48
setCaption(
"
Working!
"
);
49
qApp
->
unlock();
50
51
//
这可能会占用一些时间,几秒、几分钟或者几小时等等,因为这个一个和GUI线程分开的线程,在处理事件时,GUI线程不会停下来……
52
do_complicated_thing();
53
}
54
}
55
};
56
57
//
主线程――所有的GUI事件都由这个线程处理。
58
int
main(
int
argc,
char
**
argv )
59
{
60
QApplication app( argc, argv );
61
62
//
创建一个worker……当我们这样做的时候,这个worker将在一个线程中运行
63
Worker firstworker(
0
,
"
worker
"
);
64
65
app.setMainWidget(
&
worker );
66
worker.show();
67
68
return
app.exec();
69
}
7.线程安全类
非线程安全类
这个类不是线程安全的,因为假如多个线程都试图修改数据成员 n,结果未定义。这是因为c++中的++和--操作符不是原子操作。实际上,它们会被扩展为三个机器指令:
1,把变量值装入寄存器
2,增加或减少寄存器中的值
3,把寄存器中的值写回内存
假如线程A与B同时装载变量的旧值,在寄存器中增值,回写。他们写操作重叠了,导致变量值仅增加了一次。很明显,访问应该串行化:A执行123步骤时不应被打断。
class
Counter
{
public
:
Counter() {n
=
0
;}
void
increment() {
++
n;}
void
decrement() {
--
n;}
int
value()
const
{
return
n;}
private
:
int
n;
};
线程安全类
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函数。