Discussions related to Visual Prolog
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Per-thread (class) facts

Post by Martin Meyer »

I think it can be useful to have some kind of class facts, so that a class fact does not have the same value in all threads, but has a separate value for each thread. Such kind of facts are not built-in in the VIP language, but can be implemented via your code:

Code: Select all

class perThreadVarM{@Term} : varM{@Term} end class perThreadVarM   %---   implement perThreadVarM{@Term}   facts     varMapM : mapM{multiThread_native::threadId ThreadId, varM{@Term}} := mapM_redBlack_cas::new() [immediate].   clauses     new() :-         ThreadHandle = multiThread_native::getCurrentThread(),         ThreadID = multiThread_native::getThreadID(ThreadHandle),         VarM = varM::new_erroneous(),         varMapM:set(ThreadId, VarM),         ActualThreadHandle = multiThread_api::mkNonInheritable(ThreadHandle, :CloseOriginal = false),         SyncObject = syncObject::new(ActualThreadHandle, :NeedRelease = false),         _Future =             pfc\asynchronous\future::submit_unit(                 {  :-                     _ = SyncObject:waitAsync(),                     SyncObject:close(),                     varMapM:removeKey(ThreadID)                 },                 :Ctx = pfc\asynchronous\executionContext_pool::defaultPool).   properties     varM : varM{@Term} (o).   clauses     varM() = varMapM:get(multiThread_native::getCurrentThreadId()).   delegate     interface varM{@Term} to varM   end implement perThreadVarM
Now a variable can be created at class level which has an own value in each thread:

Code: Select all

class facts     myPerThreadVarM : varM{string} := perThreadVarM::new().
Regards Martin
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Re: Thread terminates listener

Post by Martin Meyer »

But what happens if myPerThreadVarM has been set to a value in a task and then the task is suspended? The reference teaches:
The key concern with suspension is understanding what happens when execution is paused. ... In essence, when a predicate suspends, the thread that was executing its code can be utilized for other tasks.
Can the value, which has been set in the suspended task, be visible resp. overwritten in another task which utilizes the suspended task's thread? Could it be that when the first task resumes, it now has a different thread ID and handle?

If so, how to modify your code to account for the suspension? Is there a task ID that could be used as a key instead of the thread ID?
Regards Martin
User avatar
Thomas Linder Puls
VIP Member
Posts: 1471
Joined: 28 Feb 2000 0:01

Re: Per-thread (class) facts

Post by Thomas Linder Puls »

Yes, this may be handy in some cases. There do exist some other means for "thread local variables" (some features in that area has recently been evolved, because suspending predicates needs some thread local variables at the runtime level).

But thread local variables are not likely to be useful for suspending predicates (on programmer level).

Let us use your "task" word for some job/algorithm we want to execute. In a traditional multi-threaded world we would create a thread to execute/perform the task. Subsequently the task and the thread are "synonym" to each other, and a "task" variables and thread variables therefore also "fits" nicely together. When the task is done the thread exits and if a thread is not running the task is either completed or something has gone wrong, etc, etc.

In a suspending world there are also threads, but task and thread are not synonym to each other. A single task can switch between several threads and a single thread can switch between several tasks. So in a suspending world per-thread variables cannot be used as task variables. Thread life time and task completion is not related to each other, etc., etc. So in a suspending world "tasks" needs to be handled in another way. Currently, your main "handle" to a task is the future (which we tend to throw away) you receive when you "submit" the task for execution.
Regards Thomas Linder Puls
PDC
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Re: Per-thread (class) facts

Post by Martin Meyer »

While coding per-thread (class) facts seems to be possible, I suppose there are several difficulties with coding per-task (class) facts. Probably it is not feasible to code them at the moment, right? The problems I see are:
  • How to determine whether currently in a suspending predicate?
  • If currently in a suspending predicate, then how to get its own future?
  • It is impossible to store objects of type future{ResultType} which have arbitrarily many different ResultType domains in one collection. I suppose like a thread ID for threads, an unsigned (or similar) future ID is required.
Regards Martin
User avatar
Thomas Linder Puls
VIP Member
Posts: 1471
Joined: 28 Feb 2000 0:01

Re: Per-thread (class) facts

Post by Thomas Linder Puls »

You cannot take the future/task relation too literal. The "default" way to execute a suspending predicate will return a future. But this is just "the way it happens to be"; there is nothing in the concept of suspending predicates that dictates that there have to be exist a related future. Suspending predicate "tasks" can submit other "tasks" for execution and these may be considered sub-tasks or independent top-level tasks (or something in between), and as such potentially related to several futures. Which "tasks variables" they would need access to is not easy to say.

Regarding storage of future{something}. This is as you point out not within the language as such, you can however easily obtain the "handle" you talk about as:

Code: Select all

Handle = uncheckedConvert(pointer, convert(object, Future))
By first converting it to object you ensure that you get the "base" handle, and by keeping it as a pointer you ensure that it stays under garbage collector control.
Regards Thomas Linder Puls
PDC
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Re: Per-thread (class) facts

Post by Martin Meyer »

I wonder whether I actually need per-task class facts (or an even more clever concept in the suspending world). Probably a per-thread class fact construct is sufficient for my purposes:

I am moving my code to VIP 11. My class, which does not create objects, uses class facts, and I intend to make its predicates working in case they will be called from suspending predicates. My code, since it comes from an earlier VIP version, does not call suspending predicates and does not initiate suspending by itself. No data may be shared between my predicates running in different threads.

Could it somehow happen that the execution is suspended in my routines if they are called in a suspending predicate?

If you answer “no” to my question, then I assume that changing the class facts in my code to a per-thread class fact construct suffices to make the predicates callable in suspending predicates, right?
Regards Martin
User avatar
Thomas Linder Puls
VIP Member
Posts: 1471
Joined: 28 Feb 2000 0:01

Re: Per-thread (class) facts

Post by Thomas Linder Puls »

Suspension only takes place in suspending code (disregarding certain internals of the low-level routines). But suspending predicates may run in an executionContext_pool, which is a thread pool. The threads in such a thread pool are normal threads and they follow normal threading rules. With so called pre-emptive scheduling. So even though non-suspending code will never suspend (and for example change thread) it may be scheduled/interrupted/interleaved in a normal multithreaded fashion with code executing in other threads (in the poo(s) and in you main thread, etc.)

So class facts have the same problems in a suspending context as in a normal multithreaded context.

Normally the purpose of class facts is to store "shared" data. In a single threaded context it is "just" sharing between different part of your code. In a multithreaded context it both shares between different parts of your code and between threads and "jobs/tasks", and that is more difficult to deal with.

If the class facts are intended to be shared among "jobs", "tasks" and "threads" then using "thread local facts" will not really help on anything, because that will break the sharing.

I don't believe there is a "silver bullet" that can in a general way lift global data from single threaded context to multithreaded context. In fact, I believe the opposite is true: any data that needs to be shared in a concurrent context must be carefully designed and implemented in a way that makes the sharing possible. There exist a number of tools (critical sections, compare and swap, ...) that can help, but in the end you will have to figure out how to apply them in each case.
Regards Thomas Linder Puls
PDC
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Re: Per-thread (class) facts

Post by Martin Meyer »

Ah OK, I understand that a per-thread class fact construct is not helpful.

I try it with a mutex then. Please take a look at my example. Did I do it correctly so that calls to function getNthFibNumber will work in suspending predicates?

Code: Select all

class fibonacci     open core   predicates     getNthFibNumber : (positive N) -> unsigned NthFibNumber. % Can be called in suspending predicates   end class fibonacci   %---   implement fibonacci     open core   class facts     myArM : arrayM{unsigned} := myArM_init().     myArrayMutex : mutex := mutex::create(false). % Create a mutex   class predicates     myArM_init : () -> arrayM{unsigned} MyArM. clauses     myArM_init() = MyArM :-         MyArM = arrayM::newExpandable(2),         MyArM:set(0, 0),         MyArM:set(1, 1).   class predicates     setNthFibNumber : (positive N). clauses     setNthFibNumber(N) :-         Fib2 = myArM:get(N - 2),         Fib1 = myArM:get(N - 1),         myArM:set(N, Fib2 + Fib1).   clauses     getNthFibNumber(N) = NthFibNumber :-         myArrayMutex:enter(), % Wait for ownership of the mutex         foreach I = std::fromTo(myArM:size, N) do             setNthFibNumber(I)         end foreach,         NthFibNumber = myArM:get(N),         myArrayMutex:release(). % Release ownership of the mutex   end implement fibonacci
Regards Martin
User avatar
Thomas Linder Puls
VIP Member
Posts: 1471
Joined: 28 Feb 2000 0:01

Re: Per-thread (class) facts

Post by Thomas Linder Puls »

This mail-thread is beginning to "mutate" so please consider to start new mail-threads (especially if you want to discuss compare and swap as mentioned below :-)).

Yes, that is absolutely a way to do it. I will however advise you to ensure the release using a the try-finally:

Code: Select all

clauses     getNthFibNumber(N) = NthFibNumber :-         try             myArrayMutex:enter(), % Wait for ownership of the mutex             foreach I = std::fromTo(myArM:size, N) do                 setNthFibNumber(I)             end foreach,             NthFibNumber = myArM:get(N)         finally             myArrayMutex:release() % Release ownership of the mutex         end try.
The construction above is encapsulated in criticalSection::synchronize; which can be used like this:

Code: Select all

clauses     getNthFibNumber(N) =         myArrayMutex:synchronize(             {  = NthFibNumber :-                 foreach I = std::fromTo(myArM:size, N) do                     setNthFibNumber(I)                 end foreach,                 NthFibNumber = myArM:get(N)             }).
You should notice that there exist a number of different entities that can make critical sections:
  • mutex
  • criticalSection
  • srwLock
They have different properties, and different overhead. In this particular case they can all be exchanged freely and the mutex should be the one with most overhead (but I have never investigated how much it means).

If however you want to completely encapsulate an entire class in a critical region like that you can use a monitor:

Code: Select all

monitor class fibonacci     open core   predicates     getNthFibNumber : (positive N) -> unsigned NthFibNumber. % Can be called in suspending predicates   end class fibonacci   %---   implement fibonacci ... clauses     getNthFibNumber(N) = NthFibNumber :-         foreach I = std::fromTo(myArM:size, N) do             setNthFibNumber(I)         end foreach,         NthFibNumber = myArM:get(N).   end implement fibonacci
Without going into details it may also be possible to protect data structures in a non-blocking lock free way using atomic level operations like interlocked<Something> some of which has been encapsulated in the class compareAndSwap. You can look for usages of the class in PFC and see if can figure out how it works :-). I enjoy inventing these lock-free things, it often requires some "puzzle" solving. assert and retract of facts have been made thread-safe using such techniques.
Regards Thomas Linder Puls
PDC
Martin Meyer
VIP Member
Posts: 354
Joined: 14 Nov 2002 0:01

Re: Per-thread (class) facts

Post by Martin Meyer »

Thank you Thomas for the detailed explanation!

For more I will start a new mail-thread ...
Regards Martin