Help with Mutexes

Hello,

I have entered the forbidden land ruled by multi-threading and mutexes, and would like to ask for help.

I am using first_solution/3 to find a solution to my problem as follows:
program(Goal):-
mutex_create(myMutex),
first_solution(_, [strategy_1(Goal, myMutex), strategy_2(Goal, myMutex)], []).

strategy_k(Goal, Mutex):-
% Stuff K
with_mutex(Mutex, read_text_file(SomeFile))
% More stuff K

The threads created by first_solution/3 need to access a text file; and without using a mutex to protect access to the file, IO errors are raised. My mutex solution is the one above but it does not seem to work. Only the first thread is able to access the text file (Text-file access happens inside read_text_file/1 above using see/1, read/1, and seen/0).

Could you please walk me through fixing this?

Thanks.

1 Like

I found the problem. It had nothing to do with the use of mutexes to protect access to a file.

I changed:
first_solution(_, [strategy_1(Goal, myMutex), strategy_2(Goal, myMutex)], []).

to:
first_solution(_, [strategy_1(Goal, myMutex), strategy_2(Goal, myMutex)], [on_fail(continue)]).

So that if one thread fails, the others are allowed to continue.

I misinterpreted the behaviour I was observing: the first thread Th1 which runs the first and quickest strategy fails to find the goal almost immediately; and all other threads Th2, Th3, etc. are killed ultimately (default behaviour in first_solution/3 is on_fail(stop)). Since I have no clue how to check a thread for pulse (to know whether or not it is alive or dead), the whole thing made me think that threads Th2, Th3, etc. are still running, and are being blocked awaiting for the release of the mutex to access the text file.

1 Like

Without full code to view and test it is getting a little hard. The combination of first_solution/3 and mutexes that are held for a longer time might be problematic. My first approach would be to get rid of the mutex altogether. In the context of Prolog, given non-determinism, they do not fit well and preferably thread communication should use message queues. Why precisely is this needed, i.e., what are you doing with the file that requires a mutex?

The file that needs the mutexes is being read at the beginning of each thread to populate the knowledge base with relevant information. It contains clauses that describe the problem to solve.

Are the threads reading a different file and thus build a different knowledge base? In that case Paulo’s suggestion to use thread_local might apply. If they are loading the same thing, why not load it before calling first_solution?

In fact loading the file before calling first_solution/3 is more attractive and code-efficient; but because of my poor understanding of how multi-threading works in SWI-Prolog, I decided I should read the text file from inside the thread as opposed to before starting threads.

For example, the text file contains my_clause(a), and in my program I am declaring :- thread_local my_clause/1.

From my understanding, when a thread is spawned by first_solution/3, it starts with an empty clause list for the predicate :- thread_local my_clause/1, and when it accesses the text file, it should update the knowledge base with my_clause(a). However, when the text file is read before first_solution/3, my understanding (probably wrong) is that my_clause(a) is loaded in the “non-thread specific” knowledge base but then is not included in the knowledge bases of the spawned threads – but I need the threads to be aware of my_clause(a).

For this understanding of mine, I thought reading the file from within the threads is better, and hence the use of the mutexes.

So, you have a file that you load into a knowledge base and this knowledge base is subsequently modified by the reasoners? In that case, using thread_local and reading inside the created threads makes sense. I do not get why you would need a mutex for accessing the file though. You can open a file multiple times for reading. Where do the threads influence each other? Alternatively, you could split the data into a read-only database and a read/write one per thread. Then you can load before starting the threads.

A slight aside, if it’s the same file, I’d read it once and pass the data to each thread.

If I don’t use a mutex I get the error:
ERROR: -g program(Goal): read/1: stream current_input’ does not exist`

I will use an augmented version of the example program used before in this thread:
testing_mutexes.pl (980 Bytes) which accesses a text file text_file.txt that contains the line clause1(a,b)..

The predicates program1/1 and program2/1 perform essentially the same thing. Predicate program1/1 does not use mutexes when accessing the text file, and program2/1 uses mutexes.

:- use_module(library(system)).

/*
Without mutex
*/
program1(Goal):-	
	first_solution(_,
				 	[strategy_1(Goal), 
				 	strategy_2(Goal)], 
				 	[]).

/*
With mutex
*/	
program2(Goal):-
	mutex_create(myMutex),
	first_solution(_,
				 	[strategy_1(Goal, myMutex), 
				 	strategy_2(Goal, myMutex)], 
				 	[]),
	mutex_destroy(myMutex).

strategy_1(Goal):-
	read_text_file('text_file.txt', Goal),
	write('Thread 1 says: '), write(Goal), nl.
	
strategy_2(Goal):-
	read_text_file('text_file.txt', Goal),
	write('Thread 2 says: '), write(Goal), nl.	
				 	
strategy_1(Goal, Mutex):-
	with_mutex(Mutex, read_text_file('text_file.txt', Goal)),
	write('Thread 1 says: '), write(Goal), nl.
	
strategy_2(Goal, Mutex):-
	with_mutex(Mutex, read_text_file('text_file.txt', Goal)),
	write('Thread 2 says: '), write(Goal), nl.	

read_text_file(File, Goal):-
        see(File),        
        read(Goal),        
        seen.		

If I run program1/1, I get:
Thread 1 says: clause1(a,b)
ERROR: -g program1(Goal): read/1: stream current_input’ does not exist`

If I run program2/1, I get (sometimes only one thread returns):
Thread 1 says: clause1(a,b)
Thread 2 says: clause1(a,b)

So from this behaviour I thought of using mutexes.

1 Like

This seems a double bug. current_input should be per thread, so I do not really understand why this goes wrong. As is though, if the thread gets cancelled after the see/1 call you never close the file. The
clean way is

read_text_file(File, Goal) :-
     setup_call_cleanup(
         open(File, read, In),
         read(In, Goal),
         close(In)).

But, as @nuclear_ares says, why not read the term before and pass it along with the goals to first_solution/3?

I still haven’t got the chance to change my code and test it according to @nuclear_ares’s suggestion :slight_smile: But it looks like the way forward to me.

I changed my code so that the knowledge base I read from the file is asserted after the thread is created. No need for mutexes anymore.

2 Likes