thread-safety.txt 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 1. Thread safety of metadata structures
  2. ----------------------------------------
  3. 1.1 Synchronization of read-only data
  4. -------------------------------------
  5. Read-only data is data which is not modified after creation, like the
  6. actual binary metadata in the metadata tables.
  7. There are three kinds of threads with regards to read-only data:
  8. - readers
  9. - the creator of the data
  10. - the destroyer of the data
  11. Most threads are readers.
  12. - synchronization between readers is not neccesary
  13. - synchronization between the writers is done using locks.
  14. - synchronization between the readers and the creator is done by not exposing
  15. the data to readers before it is fully constructed.
  16. - synchronization between the readers and the destroyer: TBD.
  17. 1.2 Deadlock prevention plan
  18. ----------------------------
  19. Hold locks for the shortest time possible. Avoid calling functions inside
  20. locks which might obtain global locks (i.e. locks known outside this module).
  21. 1.3 Locks
  22. ----------
  23. 1.3.1 Simple locks
  24. ------------------
  25. There are a lot of global data structures which can be protected by a 'simple' lock. Simple means:
  26. - the lock protects only this data structure or it only protects the data structures in a given C module.
  27. An example would be the appdomains list in domain.c
  28. - the lock is only held for a short amount of time, and no other lock is acquired inside this simple lock. Thus there is
  29. no possibility of deadlock.
  30. 1.3.2 The class loader lock
  31. ---------------------------
  32. This locks is held by the class loading routines in class.c and loader.c. It
  33. protects the various caches inside MonoImage which are used by these modules.
  34. 1.3.3 The domain lock
  35. ---------------------
  36. Each appdomain has a lock which protects the per-domain data structures.
  37. 1.3.4 The locking hierarchy
  38. ---------------------------
  39. It is useful to model locks by a locking hierarchy, which is a relation between locks, which is reflexive, transitive,
  40. and antisymmetric, in other words, a lattice. If a thread wants to acquire a lock B, while already holding A, it can only
  41. do it if A < B. If all threads work this way, then no deadlocks can occur.
  42. Our locking hierarchy so far looks like this:
  43. <DOMAIN LOCK>
  44. \
  45. <CLASS LOADER LOCK>
  46. \ \
  47. <SIMPLE LOCK 1> <SIMPLE LOCK 2>
  48. 1.4 Notes
  49. ----------
  50. Some common scenarios:
  51. - if a function needs to access a data structure, then it should lock it itself, and do not count on its caller locking it.
  52. So for example, the image->class_cache hash table would be locked by mono_class_get().
  53. - there are lots of places where a runtime data structure is created and stored in a cache. In these places, care must be
  54. taken to avoid multiple threads creating the same runtime structure, for example, two threads might call mono_class_get ()
  55. with the same class name. There are two choices here:
  56. <enter mutex>
  57. <check that item is created>
  58. if (created) {
  59. <leave mutex>
  60. return item
  61. }
  62. <create item>
  63. <store it in cache>
  64. <leave mutex>
  65. This is the easiest solution, but it requires holding the lock for the whole time which might create a scalability problem, and could also lead to deadlock.
  66. <enter mutex>
  67. <check that item is created>
  68. <leave mutex>
  69. if (created) {
  70. return item
  71. }
  72. <create item>
  73. <enter mutex>
  74. <check that item is created>
  75. if (created) {
  76. /* Another thread already created and stored the same item */
  77. <free our item>
  78. <leave mutex>
  79. return orig item
  80. }
  81. else {
  82. <store item in cache>
  83. <leave mutex>
  84. return item
  85. }
  86. This solution does not present scalability problems, but the created item might be hard to destroy (like a MonoClass).
  87. - lazy initialization of hashtables etc. is not thread safe