123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187(** Variables with Recursive Mutex *)(* WARNING contrary to what the OCaml Mutex doc seems to say: (fixed
in ocaml 4.12)
https://caml.inria.fr/pub/docs/manual-ocaml/libref/Mutex.html
calling Mutex.lock on a mutex locked by the SAME thread will ALSO block.
TODO? rewrite everything to use only simple, non-recursive mutex?
TODO starting from ocaml 4.12 one could use the Atomic module in
order to avoid locking. Anyways, for single reading/assignement, it
seems that we can get rid of locks. See
https://discuss.ocaml.org/t/threads-and-atomicity-of-reading-and-assignement/8923/3
Remark: if optimization is needed, one could check whether
[Utils.threads_created <> 0] before playing with mutexes. *)openB_utilstype'at={mutabledata:'a;mutablethread_id:intoption;(* = the id of the thread currently locking this var *)mutex:Mutex.t;}letcreatedata={data;thread_id=None;mutex=Mutex.create();}(* lock *)letprotectv=Mutex.lockv.mutex;v.thread_id<-SomeThread.(id(self()))(* unlock *)letreleasev=matchv.thread_idwith|Someiwheni=Thread.(id(self()))->Mutex.unlockv.mutex;v.thread_id<-None|Somei->printd(debug_thread+debug_error)"Thread %u cannot release variable locked by thread %u"Thread.(id(self()))i|None->printd(debug_thread+debug_error)"Trying to release a variable that was not locked"(* Execute an action on the given variable if it is not locked by *another*
thread. Can be used in recursions. *)letprotect_dovaction=letwas_free=Mutex.try_lockv.mutexinifwas_freethenbegin(* this should be the vast majority of cases *)(* The variable is now locked *)if!debugthenassert(v.thread_id=None);(* just for debugging *)v.thread_id<-SomeThread.(id(self()));letresult=tryaction()withexn->releasev;raiseexninreleasev;(* The variable is now unlocked *)resultendelseifv.thread_id=Some(Thread.(id(self())))thenbeginprintd(debug_thread+debug_warning)"!! We can access the variable because it was locked by same thread.";action()endelsebegin(* the variable was already locked by another thread *)printddebug_thread"Waiting for locked variable (thread #%i) to unlock..."(defaultv.thread_id(-1));protectv;printddebug_thread"...ok, the variable was unlocked, we proceed.";letresult=tryaction()withexn->releasev;raiseexninreleasev;resultend(** REMARK: The {!Utils.( let@ )} syntax can be convenient
[let@ x = with_protect v in f x].
WARNING: end of scope can be easily forgotten. See
remarks in Utils.( let@ ). *)letwith_protectvf=let@()=protect_dovinfv.dataletprotect_fn=with_protectletupdate_getvf=let@()=protect_dovinletres=fv.datainv.data<-res;resletupdatevf=let@()=protect_dovinletres=fv.datainv.data<-res(* Just getting the value without locking will not corrupt the
data. However, if another thread is playing with the value it may
put it in an intermediate state which is not supposed to be a valid
value until the computation is done. Hence it's safer to check the
lock for reading. *)(* See also
https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem
TODO? starting 4.12, use Atomic? *)letgetv=protectv;letres=v.datainreleasev;resletunsafe_getv=v.dataletset_oldvvalue=Mutex.lockv.mutex;v.data<-value;Mutex.unlockv.mutex(* [safe_set] should be used when we want to register which thread is setting
this value. (After assignement, thread_id is set back to None). Thus, this
prevents other threads to modify the value at the same time. But in Ocaml,
assignement is (essentially?) atomic. Hence, for the moment I don't see in
which case [safe_set] should be required... *)letsetvvalue=let@()=protect_dovinv.data<-value(* [unsafe_set] will set the value without locking nor touching the
thread_id field. *)letunsafe_setvvalue=v.data<-valueletincrv=protectv;v.data<-v.data+1;releasevletdecrv=protectv;v.data<-v.data-1;releasev(*******)(* for initialization of global constant by a lazy eval *)(* TODO: use Lazy module? *)exceptionNot_initializedtype'ainit={mutableinit:unit->'a;(* the function which creates the value *)var:('aoption)t}letinitinit={init;(* ou Var ? *)var=createNone}letcreate_init()=init(fun()->raiseNot_initialized)letset_initif=i.init<-f;seti.varNoneletinit_geti=protect_fni.var(function|None->letdata=i.init()inseti.var(Somedata);data|Somed->d)(*
ocamlmktop -thread -custom -o threadtop unix.cma threads.cma -cclib -lthreads
*)(*
Local Variables:
tuareg-interactive-program:"ocaml unix.cma"
typerex-interactive-program:"./threadtop -I +threads"
compile-command:"make -k"
End:
*)