I have question about selecting a WorkArea in a multithread
application.
I have found that functions like
(wArea)->VODBGotop() use the internal function _PushTempArea()
But is this function Thread-friendly ??
I mean : when I push this area, and the application switch to another
thread, would this second thread knows his own WorkArea, or get it the
workarea of the previous thread ?
Tanx for your comments
Dirk (Belgium)
Work areas are global, so they are shared across ALL threads. If you need
separate workareas, open the same file several times with different aliases
(or use DBServers) and use one per thread. I've never tried this, and I'm
not sure what it buys you in performance.
I have protected work areas with critical sections in the past to stop two
threads talking to an area at the same time. Ginny's the authority on
threads and workareas - do a Google and you'll find more than you need.
HTH
Dave francis
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:c8ebk097mkh0kuahn...@4ax.com...
I don't know if I'm the authority, but I do have multithreaded VO apps in
the field for many years now without problems, and I NEVER share workareas
across threads. There's really no reason to do so since it's so easy to just
open DBF files for multiuser access anyway.
--
Ginny
"Dave Francis" <dave_IsASp...@suilven.com> wrote in message
news:2qluqtF...@uni-berlin.de...
Ok, I understand what you say, but consider the following.
1 app with 2 threads
Thread A opens Workarea 1023 "Customers"
Thread B opens Workarea 1022 "Invoices"
Now, Thread A is doing statements like
(AliasA)->VODBSeek(...)
During this statement, Thread B is activated doing something like
(AliasB)->VODBAppend(...)
During this stament, the program switched back to Thread A.
So, I ask myself what would be the reason that ThreadA will work with
AliasA, and not work with AliasB (and crash of course) because VO has
only 1 global variable holding the "current" workarea.
I know already that the statement (AliasA)->VOxxx in the first place
call the function _PushTempArea()... witch place the AliasA in the
global variable, and after ending the VOxxx-function, he calls
_PopTempArea(), which place back the previous Area in the global
variable. If between the _PushTempArea() and the _PopTempArea() the
application is switching from Thread A to Thread B, that MUST go
wrong!!!
Ginny, please your opinion ?
(Or are you working not with the VODBxxx-functions ?)
(What functions are you using then ? DBServer-objects ?)
(Please tell me more, our where to find ?)
Dirk, Belgium
On Mon, 13 Sep 2004 17:48:56 -0400, "Ginny Caughey"
<ginny.caug...@wasteworks.com> wrote:
> Ok, I understand what you say, but consider the following.
> 1 app with 2 threads
> Thread A opens Workarea 1023 "Customers"
> Thread B opens Workarea 1022 "Invoices"
> Now, Thread A is doing statements like
(AliasA)->>VODBSeek(...)
> During this statement, Thread B is activated doing something like
(AliasB)->>VODBAppend(...)
> During this stament, the program switched back to Thread A.
> So, I ask myself what would be the reason that ThreadA will work with
> AliasA, and not work with AliasB (and crash of course) because VO has
> only 1 global variable holding the "current" workarea.
Each thread has its own current workarea (and default directory, path,
Find..() data, NetErr() etc.
> I know already that the statement (AliasA)->VOxxx in the first place
> call the function _PushTempArea()... witch place the AliasA in the
> global variable, and after ending the VOxxx-function, he calls
> _PopTempArea(), which place back the previous Area in the global
> variable. If between the _PushTempArea() and the _PopTempArea() the
> application is switching from Thread A to Thread B, that MUST go
> wrong!!!
What makes you think this process fails. Do you have an example ?
--
Robert van der Hulst
AKA Mr. Data
Vo2Jet & Vo2Ado Support
VO Development Team
www.sas-software.nl
mrdata...@sas-software.com
As Robert explained, this should work fine since the workareas are separate
for the each thread. I store the actual workarea for function access to DBF
files in a local variable using myVar := DBGetSelect() as soon as I open a
file, then I access the file like this:
? (myVar)->CustNum
(myVar)->(DBSkip(1))
If you use a DBServer object which is local to each thread, you'd get
essentially the same result.
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:n96dk0tftuevqtai8...@4ax.com...
I think this must go wrong.
But I still working VO2.5b3, maybe that's the reason ? (or not ?)
I sometimes get an error like :
Error Description
--------------------------
Subsystem :BASE
Operation :GET
Function :MEMVAR
Argument :VKLNUMMER
Where #VKLNUMMER is a field in the database !!!!
Dirk
This should work in 2.5b3 too as far as I know. What does the whole line of
code look like?
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:ihsdk09vdarqk81m5...@4ax.com...
> Each thread has its own current workarea (and default directory, path,
> Find..() data, NetErr() etc.
Are these thread-owned items and functions documented anywhere?
Dave Francis
The workarea (and therefore the info about it such as directory, path,
record pointer location, etc.) is only "owned" by a thread if you open the
workarea and assign the workarea ID to a local variable within a thread and
reference it always using that local variable (or use a DBServer object that
is local to a thread). Workareas in general are global and defintiely not
thread safe.
--
Ginny
"Dave Francis" <dave_IsASp...@suilven.com> wrote in message
news:2qog1cF...@uni-berlin.de...
> I think this must go wrong.
> But I still working VO2.5b3, maybe that's the reason ? (or not ?)
>
> I sometimes get an error like :
>
> Error Description
> --------------------------
> Subsystem :BASE
> Operation :GET
> Function :MEMVAR
> Argument :VKLNUMMER
>
Follow Ginny's advice here....
>I store the actual workarea for function access to DBF
>files in a local variable using myVar := DBGetSelect() as soon as I open a
>file, then I access the file like this:
>? (myVar)->CustNum
>(myVar)->(DBSkip(1))
Perhaps whats creating the problem is that when you open the file, your not
specifying a different alias. Create it using somekind of temp variable.
I have seen this error before. Somewhere your closing the file, perhaps in
a different thread, and this thread is trying to 'read' it, but it's already
been closed.
I would go a step further and avoid even local dbservers in different
threads. Create a unique alias, use it, then use Ginny's code from above,
DBGetSelect()...etc.
HTH,
Marshall
> Are these thread-owned items and functions documented anywhere?
No (not yet?). If you have specific questions just ask. I will answer
questions if I can find the time.
> but I do have multithreaded VO apps in the field for many years now >
without problems
I think that makes you the authority. <g>
Dave Francis
> No (not yet?). If you have specific questions just ask. I will answer
> questions if I can find the time.
Kind of you to offer but your priorities must be elsewhere at the moment.
<g>.
A lot of NGers have tried multi-threading with varying success. But we do
need a definitive model of the way VO handles these things. Perhaps the best
solution is to update and improve the white papers when there is time?
Dave Francis
I'm now very sure that (wArea)->VODBxxx is NOT working correctly in a
multithread application.
Let me explain:
Function MyThread(....)
....
EnterCriticalSection(@csABC)
Do Case
Case x=1
DoitNow1(...)
Case x=2
DoitNow2(...)
Endcase
LeaveCriticalSection(@csABC)
.....
ExitVOThread(0)
Function DoitNow1(...)
...
(wArea1)->VODBxxx(...)
....
Return
Function DoitNow2(...)
...
(wArea1)->VODBxxx(...)
....
Return
You see the structure of my program, very simplified.
When I run this program, there are no problems at all, because of the
"EnterCriticalSection" and the "LeaveCriticalSection". When I place
this 2 lines in comment, then the program crashed everytime.
The functions DoitNow1() and DoitNow2() are very long functions !!!
So, threads are switching during executing this functions. No dought
about this. Also, these functions contains 100 times
VODBxxx-functions, all with the "(wArea1)->" or "(wArea2)" included.
So, I have spent many hours to change the program, and now I delete
the "Enter&LeaveCriticalSections" in the main loop, and place all
(wArea)->VODBxxx functions between an "EnterCS" and a "LeaveCS", like
shown below.
EnterCriticalSection()
(wArea)->VODBxxx-function
LeaveCriticalSection()
This program was still crashing !!!
Second trial was the following:
EnterCriticalSection()
VODBSetSelect(wArea)
VODBxxx-function
LeaveCriticalSection()
Now, the program works without any problem !!!
So, I'm sure that (wArea)->VODB... is not thread-safe.
But, the real problem is the "Enter&LeaveCS"
- First, it slows down my program
- Second, It is a fuck-solution for a VO-problem, because each thread
is doing this for 100 times in the function and one thread has to wait
for the other thread.
Has someone the same experience ?
What are the solutions ?
Can I use the (hidden) build-in dbaXXX-functions ?
Dirk
You still didn't show us how wArea is declared and where you are opening the
file. Can you show us a very small complete app that illustrates the problem
except for the long stuff in DoItNow?
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:g3agk0dnfq2k0jba3...@4ax.com...
Opening the file is done in the thread, as follow :
cAlias := "Klanten_"+NTrim(GetCurrentThreadId())
wArea := IPDBSetSelect(-1)
aRdds := __RddList("DBFCDX",{})
rddList := __AllocRddList(aRdds)
lSuccess := IPDBUseArea(wArea,FALSE,rddList,;
cFileName,cAlias,DBSHARED,DBREADWRITE)
MemFree(rddList)
IF lSuccess
IF ((wArea)->FieldPosSym(#VDATUM))=0
AltD()
ENDIF
ENDIF
wArea is now put in a global array.
Reading and writing to this global array is put into a
CriticalSection(@csGlobal)
Sometimes, the application stops on AltD().
If I look with the debugger to the Databases, then the field VDATUM is
existing, the database is (of course) open, but the expression returns
me zero.
This only happens when 2 threads are running. Never when only 1 thread
is running in this multithread application.
Dirk
> Opening the file is done in the thread, as follow :
>
> cAlias := "Klanten_"+NTrim(GetCurrentThreadId())
> wArea := IPDBSetSelect(-1)
> aRdds := __RddList("DBFCDX",{})
> rddList := __AllocRddList(aRdds)
> lSuccess := IPDBUseArea(wArea,FALSE,rddList,;
> cFileName,cAlias,DBSHARED,DBREADWRITE)
> MemFree(rddList)
>
> IF lSuccess
> IF ((wArea)->FieldPosSym(#VDATUM))=0
> AltD()
> ENDIF
> ENDIF
>
Personally, I would not try to debug a multithreaded app with the debugger.
I have had success using dbgview from www.sysinternals.com, where you can
output strings with _debout32( 'xxxx').
The IPDSetSelect(-1), returns the current work area? The -1 for a parameter
is correct? I'd start by removing the debugger. With certainty, you can do
what your trying to do with VO. Opening a dbf, in a thread, with it's own
alias, does work. I'd start by removing the Altd(). After that, you'd have
to look closer at your IPDB functions, perhaps one of them is doing
something different than you expect.
Marshall
IPDB functions are wrapper around VODB functions, like this
IPDBUseArea(...)
EnterCriticalSection(@cs_WorkArea)
VODBSetSelect(INT(_CAST,wArea))
//lSuccess := ((wArea)->VODBUseArea(lNew,rddList,;
cName,cAlias,lShare,lReadOnly))
lSuccess := VODBUseArea(lNew,rddList,
cName,cAlias,lShare,lReadOnly)
LeaveCriticalSection(@cs_WorkArea)
RETURN lSuccess
> I'm now very sure that (wArea)->VODBxxx is NOT working correctly in a
> multithread application.
> Let me explain:
> The functions DoitNow1() and DoitNow2() are very long functions !!!
> So, threads are switching during executing this functions. No dought
> about this. Also, these functions contains 100 times
> VODBxxx-functions, all with the "(wArea1)->" or "(wArea2)" included.
> EnterCriticalSection()
> (wArea)->VODBxxx-function
> LeaveCriticalSection()
> This program was still crashing !!!
> Second trial was the following:
> EnterCriticalSection()
> VODBSetSelect(wArea)
> VODBxxx-function
> LeaveCriticalSection()
> Now, the program works without any problem !!!
> So, I'm sure that (wArea)->VODB... is not thread-safe.
> But, the real problem is the "Enter&LeaveCS"
> - First, it slows down my program
> - Second, It is a fuck-solution for a VO-problem, because each thread
> is doing this for 100 times in the function and one thread has to wait
> for the other thread.
> Has someone the same experience ?
> What are the solutions ?
> Can I use the (hidden) build-in dbaXXX-functions ?
I already told you eath thread has its own current area. SO there is really
no need for constructs like:
(wArea)->VODBxxx-function
(wArea)->VODBxxx-function2
(wArea)->VODBxxx-function3
You are better off by doing
VODBSetSelect(wArea)
VODBxxx-function
VODBxxx-function2
VODBxxx-function3
It is also faster.
If you make sure each thread has its OWN WORKAREA you should not have to
worry about critical sections for Workarea access at all.
I agree with everybody else here. You are making this way harder than it
needs to be. And don't ever try to use the debugger with a multithreaded
app - I use log files myself during debugging, and I also use one for
handling runtime errors:
FUNCTION HandleError(cError AS STRING) AS VOID PASCAL
LOCAL hFile AS PTR
// Thread-safe write to log file
EnterCriticalSection(@CS_WATCHERR)
IF File(GetDefault()+"watcherr.log")
hFile := FOpen(GetDefault()+"watcherr.log", FO_READWRITE)
FSeek(hFile, 0, FS_END)
ELSE
hFile := FCreate(GetDefault()+"watcherr.log")
ENDIF
IF hFile != F_ERROR
FWrite(hFile, DToC(Today())+" "+Time24()+" "+cError+_Chr(13)+_Chr(10))
FClose(hFile)
ENDIF
LeaveCriticalSection(@CS_WATCHERR)
Here of course I do need the critical sections since more than one thread
could try to write to the same error log at a time. The critical section is
global to the app and is initialized in the Start method of the app.
Why do you have a global array of workareas anyway? You should close each
open workarea inside the thread that opens it, and your app shouldn't exit
until all of your threads have done so.
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:qscgk09kuee4c3ncp...@4ax.com...
regards,
Erik
"Dirk (Belgium)" <dirk.he...@pbprojects.be> schreef in bericht
news:c8ebk097mkh0kuahn...@4ax.com...
Dirk
Looking forward to see you there, but i understand i do not have to bring
the articles with me as you have them already.
Erik
>
>I already told you eath thread has its own current area. SO there is really
>no need for constructs like:
>
>(wArea)->VODBxxx-function
>(wArea)->VODBxxx-function2
>(wArea)->VODBxxx-function3
>
>You are better off by doing
>
>VODBSetSelect(wArea)
>VODBxxx-function
>VODBxxx-function2
>VODBxxx-function3
>
>It is also faster.
>
>If you make sure each thread has its OWN WORKAREA you should not have to
>worry about critical sections for Workarea access at all.
Robert and Ginny,
I would use the following construction
(wArea1)->VODBFieldGet(..)
(wArea2)->VODBFieldPut(..)
otherwise I have to write
VODBSetSelect(wArea1)
VODBFieldGet(...)
VODBSetSelect(wArea2)
VODBFieldPut(...)
This will make my code with more then 755 lines longer and not so
clear anymore.
Why are you saying the second method is better then the first method ?
Because I agree with you, the second method is working and the first
method Crash !
That's also the subject of this thread : _PushTempArea is this
thread-safe ??
As already explained, thread are running very fast, and I run about 10
threads on the same time, each thread has is own 15 workarea's, and
all together, they do more then 30 VODBxxxx function each second !
Why is this working (see below) :
EnterCS(x)
VODBSetSelect(wArea)
VODBxxx
LeaveCS(x)
And why is this not working (crash) :
(this is the only difference in the application)
(it is now simply to do thanks to my wrapper functions)
EnterCS(x)
(wArea)->VODBxxx
LeaveCS(x)
And this is also not working :
//EnterCS(x) -> Place in comment
VODBSetSelect(wArea)
VODBxxx
//LeaveCS(x) -> Place in comment
So, I'm not sure that each thread has his own workareaselect()
I still working in VO2.5b3. Can this be the reason.
I'm using CreateVOThread(...) and not CreateThread(...)
I wish I could believe you, but why is the above code not working
except :
EnterCs()
VODBSetSelect)(
VODBxxx
LeaveCS()
Dirk
I don't know if this is relevant, but it is a "feature" of 2.5b3...
if ALIAS1->DBSEEK('x') .and. ALIAS1->mydatafield == 'y'
will _always_ return true (or is it false?), whatever the actual outcome of
the test.
if ALIAS1->(DBSEEK('x')) .and. ALIAS1->mydatafield == 'y'
where all function calls are in backets returns the correct answer.
This is only a problem in compound queries. I have become so conservative in
my coding (so I ALWAYS use brackets), that I don't know if this has survived
into 2.6/7.
I know this is unlikely to be a factor, but I thought I ought to mention it.
Dave Francis
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:bniik0dnf4ds2pdn7...@4ax.com...
If you don't use VODBSelect(wArea), you should use
(wArea)->(VODBxxx-function). Note the parentheses. This is what my code
looks like except I use the DB functions rather than the VODB ones.
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:bniik0dnf4ds2pdn7...@4ax.com...
Are you saying that
(wArea)->VODEof()
could be different from
(wArea)->(VDDBEof())
??
And is this also different from
((wArea)->VODBEof())
This is the way I do it.
Can you explain then the difference ?
In my case, wArea is the workarea number, not an alias.
Dirk
you can bring them with you.
Then I can see I have the same articles.
Dirk
On Thu, 16 Sep 2004 09:43:58 +0200, "Erik Visser"
> Are you saying that
> (wArea)->VODEof()
> could be different from
> (wArea)->(VDDBEof())
I _know_ there is a difference if you ask the runtime (2.5b3) to return a
value from a compound IF statement, even if you do not use threads.
I think Ginny implied you needed to adopt this syntax in a worker thread -
but, like you, I await confirmation of that. (Thanks for focusing on this
detail <g>).
> ((wArea)->VODBEof())
I would say, but not definitively, this is eqivalent to
(wArea)->VODBEof()
so perhaps not the best syntax.
HTH
Dave Francis
I said that (warea)->DBFunction() is different from (warea)->(DBFunction())
which is different from warea->DBFunction - at least it was in Clipper. I
didn't say anything about those other cases. So what happened when you tried
the change I suggested?
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:dq3lk0hmv4ou199o6...@4ax.com...
I have seen Robert VDH this weekend, and we have seen that NOT each
thread has is own workarea, but that there is only one global area for
a process. But, we have not go very deep in the sources, and there may
be a change that you are still right.
So, how sure are you about that statement ?
Dirk
On Fri, 17 Sep 2004 08:08:09 -0400, "Ginny Caughey"
<ginny.caug...@wasteworks.com> wrote:
>Dirk,
>
> I have seen Robert VDH this weekend, and we have seen that NOT each
> thread has is own workarea, but that there is only one global area for
> a process. But, we have not go very deep in the sources, and there may
> be a change that you are still right.
I can't talk for 2.5b-3, but only for 2.7(a) and these are the facts:
- There are 1024 workareas per process
- Each thread has its own CURRENT workarea number, and current workarea
pointer
- All threads share the Push/Pop workarea stack
- There is NO difference between (nArea)->VoDbEof() and (nArea)->(VoDbEof())
They both generate the following code:
- Push value of nArea
- Call _PushTempUsual()
- Call VODbEof()
- Store result of VoDbEof()
- Call _PopTempSelect()
- The only possible problem that I have seen is that the code in the
_Push..() and _Pop..() functions (or called by these functions) shares the
common workarea stack. This could cause problems when the code gets
executed in the wrong order, like below:
Thread A Push
Thread B Push
Thread A Pop
Thread B Pop
So for now I advise not to use the (nArea)-> syntax until we have changed
the runtime to give each thread its own push/pop workarea stack.
"Robert van der Hulst" <mrdata...@sas-software.com> wrote in message
news:1936100.200...@sas-software.nl...
Please see my message to Robert.
--
Ginny
"Dirk (Belgium)" <dirk.he...@pbprojects.be> wrote in message
news:i11tk05jjpl2rlsmt...@4ax.com...