许多GUI应用程序是敏感的:你点击一个构件,应用程序立即就会响应。
其它应用程序是非常不敏感的。应用程序也许必须执行时间敏感型的操作或者和远程设备通讯,这些动作也许会使应用程序每次被阻塞一段时间。使这种类型的应用程序得到更多响应的一个方法是使用多线程。至少一个纤程总是可以处理用户的点击,而其它纤程可以处理阻塞的或长时间的操作。
但是现在两个线程能够在相同时间试图进入或更新同一个构件。如果发生这种情况的话,应用程序会遇到段错。这篇文章阐述如何处理以上的这个问题。
As mentioned above, we might have a program that calls a function that does some time-intensive work. It then modifies one of its widgets, most likely based on the outcome of the work just done.We'll use a simple example to clarify our points, using a "for" loop as the time-intensive part, and a PtWindow title change as the widget modification. In a real application, of course, something more useful would be done.
(你可以用 'qcc -o testapp testapp.c -lph'来编译和连接这些例子。)
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <photon/PtWindow.h>
void *thread_func(void*); void my_widget_func(PtWidget_t *w,char *text);
int main(int argc,char *argv[])
{
PtWidget_t *win; PtArg_t args[2];
PhDim_t dim={200,20};
pthread_t thread;
PtSetArg(&args[0],Pt_ARG_DIM,&dim,0);
win = PtAppInit(NULL,NULL,NULL,1,args);
PtRealizeWidget(win);
pthread_create(&thread,NULL,thread_func,win);
PtMainLoop();
}
void my_widget_func(PtWidget_t *w,char *text)
{
int eval; volatile int z;
for (z=0;z<0x1000000;z++) z=z;PtSetResource(w,Pt_ARG_WINDOW_TITLE,text,0);
}
void *thread_func(void *data)
{
PtWidget_t *win=(PtWidget_t*)data;
char title[80];
while (1) {
time_t ltime=time(NULL);
strftime(title,80,"Its %H:%M:%S", localtime(<ime));
my_widget_func(win,title);
sleep(5);
}
}
If you compile and run this program, you'll notice that the application dies of a segment violation (SIGSEGV) after five seconds. Why? QNX Photon microGUI functions aren't thread safe -- our call to PtSetResource() trips something up internally, and KABOOM!
The people at QNX Software have been kind enough to provide an answer: PtEnter() locks the Photon library, and PtLeave() unlocks the Photon library. We can make any kind of Photon library call between those two function calls. To make our example work, change the PtSetResource() call to:if ((eval=PtEnter(Pt_EVENT_PROCESS_PREVENT))>=0) { PtSetResource(w,Pt_ARG_WINDOW_TITLE,text,0); PtLeave(eval); }
We're simply calling PtEnter(), making sure it's not returning an error, then calling our PtSetResource() function, and calling PtLeave(). Works like a charm.
Let's complicate things a bit. It could be anything but, for this example, let's say you need to call our my_widget_func() function when the widget is exposed. Let's also assume that you can't, for whatever reason, move the PtEnter/PtLeave() calls outside of your widget function.
Here's the code for our callback:
int event_handler(PtWidget_t *widget,void *data,PtCallbackInfo_t *cbinfo) { my_widget_func(widget,"Time to expose"); return Pt_CONTINUE; }
and of course we need to add the handler to the widget by inserting the following line after the PtRealizeWidget() function call:
PtAddEventHandler(win,Ph_EV_EXPOSE,event_handler,NULL);
Now, we'd expect our window title to change temporarily to 'Time to expose' every time we dragged another window across it. (The window widget will get an expose event every time part of it is exposed.) But nothing happens!
This is because the Photon library is already locked by the PtMainLoop() thread. So when we try to lock it again with PtEnter(), it fails. (PtEnter() and PtLeave() calls are not recursive.)
. 2004, 2007, QNX Software Systems. All rights reserved.
We can easily solve this problem by checking if the value returned by PtEnter() is -EDEADLK.
if ((eval=PtEnter(Pt_EVENT_PROCESS_PREVENT))>=0 || eval==-EDEADLK) { PtSetResource(w,Pt_ARG_WINDOW_TITLE,text,0); if (eval>=0) PtLeave(eval); }
This works because PtEnter() is guaranteed to fail with -EDEADLK if the calling thread already owns the lock. As a result, it's safe for this thread to modify widgets or Photon function calls.
Also notice that since the lock was not obtained in this function, it is not released here either. In the code snippet above, calling PtLeave() when a PtEnter() has not been called or has returned -EDEADLK, will lead to an eventual segment violation (SIGSEGV).
Problem solved! Now everything works as we want it to. :)
Happy safe multi-threaded widget coding!
PtWidget_t *win=(PtWidget_t*)data; char title[80]; while (1) { time_t ltime=time(NULL); strftime(title,80,"Its %H:%M:%S", localtime(<ime)); my_widget_func(win,title); sleep(5); } }
If you compile and run this program, you'll notice that the application dies of a segment violation (SIGSEGV) after five seconds. Why? QNX Photon microGUI functions aren't thread safe -- our call to PtSetResource() trips something up internally, and KABOOM!
The people at QNX Software have been kind enough to provide an answer: PtEnter() locks the Photon library, and PtLeave() unlocks the Photon library. We can make any kind of Photon library call between those two function calls. To make our example work, change the PtSetResource() call to:
if ((eval=PtEnter(Pt_EVENT_PROCESS_PREVENT))>=0) { PtSetResource(w,Pt_ARG_WINDOW_TITLE,text,0); PtLeave(eval); }
We're simply calling PtEnter(), making sure it's not returning an error, then calling our PtSetResource() function, and calling PtLeave(). Works like a charm.
Let's complicate things a bit. It could be anything but, for this example, let's say you need to call our my_widget_func() function when the widget is exposed. Let's also assume that you can't, for whatever reason, move the PtEnter/PtLeave() calls outside of your widget function.
Here's the code for our callback:
int event_handler(PtWidget_t *widget,void *data,PtCallbackInfo_t *cbinfo) { my_widget_func(widget,"Time to expose"); return Pt_CONTINUE; }
and of course we need to add the handler to the widget by inserting the following line after the PtRealizeWidget() function call:
PtAddEventHandler(win,Ph_EV_EXPOSE,event_handler,NULL);
Now, we'd expect our window title to change temporarily to 'Time to expose' every time we dragged another window across it. (The window widget will get an expose event every time part of it is exposed.) But nothing happens!
This is because the Photon library is already locked by the PtMainLoop() thread. So when we try to lock it again with PtEnter(), it fails. (PtEnter() and PtLeave() calls are not recursive.)
We can easily solve this problem by checking if the value returned by PtEnter() is -EDEADLK.
if ((eval=PtEnter(Pt_EVENT_PROCESS_PREVENT))>=0 || eval==-EDEADLK) { PtSetResource(w,Pt_ARG_WINDOW_TITLE,text,0); if (eval>=0) PtLeave(eval); }
This works because PtEnter() is guaranteed to fail with -EDEADLK if the calling thread already owns the lock. As a result, it's safe for this thread to modify widgets or Photon function calls.
Also notice that since the lock was not obtained in this function, it is not released here either. In the code snippet above, calling PtLeave() when a PtEnter() has not been called or has returned -EDEADLK, will lead to an eventual segment violation (SIGSEGV).
Problem solved! Now everything works as we want it to. :)
Happy safe multi-threaded widget coding!