123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196(** 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. It must be [None] if the
mutex is unlocked. *)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()))->v.thread_id<-None;Mutex.unlockv.mutex;|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. Warning, this is not 100% safe, see
comments. *)letprotect_dovaction=ifMutex.try_lockv.mutexthenbegin(* Mutex was not locked before; this should be the vast majority of cases. *)(* The Mutex is now locked. *)if!debugthenassert(v.thread_id=None);(* just for debugging *)v.thread_id<-SomeThread.(id(self()));letresult=tryaction()withexn->releasev;raiseexninreleasev;(* The Mutex is now unlocked. *)resultendelse(* The Mutex was already locked. *)ifv.thread_id=Some(Thread.(id(self())))thenbeginprintd(debug_thread+debug_warning)"!! We can access the variable because it was locked by same thread.";(* In the (very short, but still nonzero) meantime, the Mutex might have
been unlocked: this is not a problem; the log below will print #-1 for
the thread number. However, it might even have been unlocked and
re-locked again by another thread in the meantime (not by the self
thread, of course). Hence the action can cause data race. *)action()endelsebegin(* The Mutex 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:
*)