ATLAS Offline Software
ConcurrentMap.icc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration.
3  */
4 /**
5  * @file CxxUtils/ConcurrentMap.icc
6  * @author scott snyder <snyder@bnl.gov>
7  * @date Jan, 2023
8  * @brief Hash map from integers/pointers allowing concurrent, lockless reads.
9  */
10 
11 
12 namespace CxxUtils {
13 
14 
15 #define T_CONCURRENTMAP template <class KEY, class VALUE, \
16  template <class> class UPDATER, \
17  class HASHER, \
18  class MATCHER, \
19  detail::ConcurrentHashmapVal_t NULLVAL, \
20  detail::ConcurrentHashmapVal_t TOMBSTONE> \
21  ATH_REQUIRES (detail::IsConcurrentHashmapPayload<KEY> && \
22  detail::IsConcurrentHashmapPayload<VALUE> && \
23  detail::IsUpdater<UPDATER> && \
24  detail::IsHash<HASHER, KEY> && \
25  detail::IsBinaryPredicate<MATCHER, KEY>)
26 
27 
28 #define CONCURRENTMAP ConcurrentMap<KEY, VALUE, UPDATER, \
29  HASHER, MATCHER, \
30  NULLVAL, TOMBSTONE>
31 
32 
33 /**
34  * @brief Constructor.
35  * @param updater Object used to manage memory
36  * (see comments at the start of the class).
37  * @param capacity The initial table capacity.
38  * (Will be rounded up to a power of two.)
39  * @param ctx Execution context.
40  */
41 T_CONCURRENTMAP
42 CONCURRENTMAP::ConcurrentMap (Updater_t&& updater,
43  size_type capacity /*= 64*/,
44  const Context_t& ctx
45  /* = Updater_t::defaultContext()*/)
46  : m_impl (std::move (updater),
47  capacity,
48  Hasher(),
49  Matcher(),
50  ctx)
51 {
52 }
53 
54 
55 /**
56  * @brief Constructor from another map.
57  * @param updater Object used to manage memory
58  * (see comments at the start of the class).
59  * @param capacity The initial table capacity of the new table.
60  * (Will be rounded up to a power of two.)
61  * @param ctx Execution context.
62  *
63  * (Not really a copy constructor since we need to pass @c updater.)
64  */
65 T_CONCURRENTMAP
66 CONCURRENTMAP::ConcurrentMap (const ConcurrentMap& other,
67  Updater_t&& updater,
68  size_type capacity /*= 64*/,
69  const Context_t& ctx
70  /*= Updater_t::defaultContext()*/)
71  : m_impl (std::move (updater),
72  capacity,
73  Hasher(),
74  Matcher(),
75  ctx)
76 {
77  // not using reference, because our iterator doesn't return a reference
78  for (const auto p : other) {
79  this->put (p.first, p.second, true, ctx);
80  }
81 }
82 
83 
84 /**
85  * @brief Constructor from a range.
86  * @param f Start iterator for the range.
87  * @param l End iterator for the range.
88  * @param updater Object used to manage memory
89  * (see comments at the start of the class).
90  * @param capacity The initial table capacity of the new table.
91  * (Will be rounded up to a power of two.)
92  * @param ctx Execution context.
93  *
94  * Constructor from a range of pairs.
95  */
96 T_CONCURRENTMAP
97 template <class InputIterator>
98 CONCURRENTMAP::ConcurrentMap (InputIterator f,
99  InputIterator l,
100  Updater_t&& updater,
101  size_t capacity /*= 64*/,
102  const Context_t& ctx
103  /*= Updater_t::defaultContext()*/)
104  : m_impl (std::move (updater),
105  capacity,
106  Hasher(),
107  Matcher(),
108  ctx)
109 {
110  Lock_t lck = lock();
111  for (; f != l; ++f) {
112  this->put (lck, f->first, f->second, true, ctx);
113  }
114 }
115 
116 
117 /**
118  * @brief Return the number of items currently in the map.
119  */
120 T_CONCURRENTMAP
121 inline
122 auto CONCURRENTMAP::size() const -> size_type
123 {
124  return m_impl.size();
125 }
126 
127 
128 /**
129  * @brief Test if the map is currently empty.
130  */
131 T_CONCURRENTMAP
132 inline
133 bool CONCURRENTMAP::empty() const
134 {
135  return !m_impl.size();
136 }
137 
138 
139 /**
140  * @brief Return the current size (capacity) of the hash table.
141  */
142 T_CONCURRENTMAP
143 inline
144 auto CONCURRENTMAP::capacity() const -> size_t
145 {
146  return m_impl.capacity();
147 }
148 
149 
150 /**
151  * @brief The number of erased elements in the current table.
152  */
153 T_CONCURRENTMAP
154 inline
155 auto CONCURRENTMAP::erased() const -> size_t
156 {
157  return m_impl.erased();
158 }
159 
160 
161 /**
162  * @brief Constructor.
163  * @param it Iterator of the underlying table.
164  */
165 T_CONCURRENTMAP
166 inline
167 CONCURRENTMAP::const_iterator::const_iterator (typename Impl_t::const_iterator it)
168  : m_impl (it)
169 {
170 }
171 
172 
173 /**
174  * @brief Test if this iterator is valid.
175  *
176  * This should be the same as testing for != end().
177  */
178 T_CONCURRENTMAP
179 inline
180 auto CONCURRENTMAP::const_iterator::valid() const -> bool
181 {
182  return m_impl.valid();
183 }
184 
185 
186 /**
187  * @brief iterator_facade requirement: Increment the iterator.
188  */
189 T_CONCURRENTMAP
190 inline
191 auto CONCURRENTMAP::const_iterator::increment() -> void
192 {
193  m_impl.next();
194 }
195 
196 
197 /**
198  * @brief iterator_facade requirement: Decrement the iterator.
199  */
200 T_CONCURRENTMAP
201 inline
202 auto CONCURRENTMAP::const_iterator::decrement() -> void
203 {
204  m_impl.prev();
205 }
206 
207 
208 /**
209  * @brief iterator_facade requirement: Equality test.
210  */
211 T_CONCURRENTMAP
212 inline
213 auto CONCURRENTMAP::const_iterator::equal (const const_iterator& other) const
214  -> bool
215 {
216  return !(m_impl != other.m_impl);
217 }
218 
219 
220 /**
221  * @brief iterator_facade requirement: Dereference the iterator.
222  */
223 T_CONCURRENTMAP
224 inline
225 auto CONCURRENTMAP::const_iterator::dereference() const
226  -> const const_iterator_value
227 {
228  return const_iterator_value (keyAsKey (m_impl.key()),
229  mappedAsMapped (m_impl.value()));
230 }
231 
232 
233 /**
234  * @brief Return an iterator range covering the entire map.
235  */
236 T_CONCURRENTMAP
237 auto CONCURRENTMAP::range() const -> const_iterator_range
238 {
239  auto [begin, end] = m_impl.range();
240  return const_iterator_range (begin, end);
241 }
242 
243 
244 /**
245  * @brief Iterator at the start of the map.
246  */
247 T_CONCURRENTMAP
248 inline
249 auto CONCURRENTMAP::begin() const -> const_iterator
250 {
251  return const_iterator (m_impl.begin());
252 }
253 
254 
255 /**
256  * @brief Iterator at the end of the map.
257  */
258 T_CONCURRENTMAP
259 inline
260 auto CONCURRENTMAP::end() const -> const_iterator
261 {
262  return const_iterator (m_impl.end());
263 }
264 
265 
266 /**
267  * @brief Iterator at the start of the map.
268  */
269 T_CONCURRENTMAP
270 inline
271 auto CONCURRENTMAP::cbegin() const -> const_iterator
272 {
273  return begin();
274 }
275 
276 
277 /**
278  * @brief Iterator at the end of the map.
279  */
280 T_CONCURRENTMAP
281 inline
282 auto CONCURRENTMAP::cend() const -> const_iterator
283 {
284  return end();
285 }
286 
287 
288 /**
289  * @brief Test if a key is in the container.
290  * @param key The key to test.
291  */
292 T_CONCURRENTMAP
293 inline
294 bool CONCURRENTMAP::contains (key_type key) const
295 {
296  return get(key).valid();
297 }
298 
299 
300 /**
301  * @brief Return the number of times a given key is in the container.
302  * @param key The key to test.
303  *
304  * Returns either 0 or 1, depending on whether or not the key is in the map.
305  */
306 T_CONCURRENTMAP
307 inline
308 auto CONCURRENTMAP::count (key_type key) const -> size_type
309 {
310  return contains (key) ? 1 : 0;
311 }
312 
313 
314 /**
315  * @brief Look up an element in the map.
316  * @param key The element to find.
317  *
318  * Returns either an iterator referencing the found element or end().
319  */
320 T_CONCURRENTMAP
321 inline
322 auto CONCURRENTMAP::find (key_type key) const -> const_iterator
323 {
324  return const_iterator (this->get (key));
325 }
326 
327 
328 /**
329  * @brief Look up an element in the map.
330  * @param key The element to find.
331  *
332  * Returns the value associated with the key.
333  * Throws @c std::out_of_range if the key does not exist in the map.
334  */
335 T_CONCURRENTMAP
336 auto CONCURRENTMAP::at (key_type key) const -> mapped_type
337 {
338  typename Impl_t::const_iterator it = this->get (key);
339  if (!it.valid()) {
340  throw std::out_of_range ("ConcurrentMap::at");
341  }
342  return mappedAsMapped (it.value());
343 }
344 
345 
346 /**
347  * @brief Return a range of iterators with entries matching @c key.
348  * @param key The element to find.
349  *
350  * As keys are unique in this container, this is either a single-element
351  * range, or both iterators are equal to end().
352  */
353 T_CONCURRENTMAP
354 auto CONCURRENTMAP::equal_range (key_type key) const
355  -> std::pair<const_iterator, const_iterator>
356 {
357  const_iterator i1 = find (key);
358  const_iterator i2 = i1;
359  if (i2.valid()) {
360  ++i2;
361  }
362  return std::make_pair (i1, i2);
363 }
364 
365 
366 /**
367  * @brief Take a lock on the container.
368  *
369  * Take a lock on the container.
370  * The lock can then be passed to insertion methods, allowing to factor out
371  * the locking inside loops. The lock will be released when the
372  * lock object is destroyed.
373  */
374 T_CONCURRENTMAP
375 inline
376 auto CONCURRENTMAP::lock() -> Lock_t
377 {
378  return m_impl.lock();
379 }
380 
381 
382 /**
383  * @brief Add an element to the map.
384  * @param key The key of the new item to add.
385  * @param val The value of the new item to add.
386  * @param ctx Execution context.
387  *
388  * This will not overwrite an existing entry.
389  * The first element in the returned pair is an iterator referencing
390  * the added item. The second is a flag that is true if a new element
391  * was added.
392  */
393 T_CONCURRENTMAP
394 inline
395 auto CONCURRENTMAP::emplace (key_type key,
396  mapped_type val,
397  const Context_t& ctx
398  /*= Updater_t::defaultContext()*/)
399  -> std::pair<const_iterator, bool>
400 {
401  return put (key, val, false, ctx);
402 }
403 
404 
405 /**
406  * @brief Add an element to the map, with external locking.
407  * @param lock The lock object returned from lock().
408  * @param val The value of the new item to add.
409  * @param ctx Execution context.
410  *
411  * This will not overwrite an existing entry.
412  * The first element in the returned pair is an iterator referencing
413  * the added item. The second is a flag that is true if a new element
414  * was added.
415  */
416 T_CONCURRENTMAP
417 inline
418 auto CONCURRENTMAP::emplace (const Lock_t& lock,
419  key_type key,
420  mapped_type val,
421  const Context_t& ctx
422  /*= Updater_t::defaultContext()*/)
423  -> std::pair<const_iterator, bool>
424 {
425  return put (lock, key, val, false, ctx);
426 }
427 
428 
429 /**
430  * @brief Add an element to the map, or overwrite an existing one.
431  * @param key The key of the new item to add.
432  * @param val The value of the new item to add.
433  * @param ctx Execution context.
434  *
435  * This will overwrite an existing entry.
436  * The first element in the returned pair is an iterator referencing
437  * the added item. The second is a flag that is true if a new element
438  * was added.
439  */
440 T_CONCURRENTMAP
441 inline
442 auto CONCURRENTMAP::insert_or_assign (key_type key,
443  mapped_type val,
444  const Context_t& ctx
445  /*= Updater_t::defaultContext()*/)
446  -> std::pair<const_iterator, bool>
447 {
448  return put (key, val, true, ctx);
449 }
450 
451 
452 /**
453  * @brief Add an element to the map, or overwrite an existing one,
454  * with external locking.
455  * @param lock The lock object returned from lock().
456  * @param key The key of the new item to add.
457  * @param val The value of the new item to add.
458  * @param ctx Execution context.
459  *
460  * This will overwrite an existing entry.
461  * The first element in the returned pair is an iterator referencing
462  * the added item. The second is a flag that is true if a new element
463  * was added.
464  */
465 T_CONCURRENTMAP
466 inline
467 auto CONCURRENTMAP::insert_or_assign (const Lock_t& lock,
468  key_type key,
469  mapped_type val,
470  const Context_t& ctx
471  /*= Updater_t::defaultContext()*/)
472  -> std::pair<const_iterator, bool>
473 {
474  return put (lock, key, val, true, ctx);
475 }
476 
477 
478 /**
479  * @brief Add an element to the map.
480  * @param p The item to add.
481  * Should be a pair where first is the key
482  * and second is the integer value.
483  *
484  * This will not overwrite an existing entry.
485  * The first element in the returned pair is an iterator referencing
486  * the added item. The second is a flag that is true if a new element
487  * was added.
488  *
489  * For external locking, use emplace().
490  */
491 T_CONCURRENTMAP
492 template <class PAIR>
493 inline
494 auto CONCURRENTMAP::insert (const PAIR& p)
495  -> std::pair<const_iterator, bool>
496 {
497  return emplace (p.first, p.second);
498 }
499 
500 
501 /**
502  * @brief Insert a range of elements to the map.
503  * @param first Start of the range.
504  * @param last End of the range.
505  *
506  * The range should be a sequence of pairs where first is the string key
507  * and second is the integer value.
508  */
509 T_CONCURRENTMAP
510 template <class InputIterator>
511 void CONCURRENTMAP::insert (InputIterator first, InputIterator last)
512 {
513  Lock_t lck = lock();
514  const Context_t& ctx = Updater_t::defaultContext();
515  for (; first != last; ++first) {
516  emplace (lck, first->first, first->second, ctx);
517  }
518 }
519 
520 
521 /**
522  * @brief Erase an entry from the table.
523  * @param key The key to erase.
524  *
525  * Mark the corresponding entry as deleted.
526  * Return true on success, false on failure (key not found).
527  *
528  * The tombstone value must be different from the null value.
529  *
530  * This may cause the key type returned by an iterator to change
531  * asynchronously to the tombstone value.
532  **/
533 T_CONCURRENTMAP
534 inline
535 bool CONCURRENTMAP::erase (key_type key)
536 {
537  val_t kval = keyAsVal (key);
538  size_t hash = m_impl.hasher() (kval);
539  return m_impl.erase (kval, hash);
540 }
541 
542 
543 /**
544  * @brief Erase an entry from the table, with external locking.
545  * @param lock The lock object returned from lock().
546  * @param key The key to erase.
547  *
548  * Mark the corresponding entry as deleted.
549  * Return true on success, false on failure (key not found).
550  *
551  * The tombstone value must be different from the null value.
552  *
553  * This may cause the key type returned by an iterator to change
554  * asynchronously to the tombstone value.
555  **/
556 T_CONCURRENTMAP
557 inline
558 bool CONCURRENTMAP::erase (const Lock_t& lock, key_type key)
559 {
560  val_t kval = keyAsVal (key);
561  size_t hash = m_impl.hasher() (kval);
562  return m_impl.erase (lock, kval, hash);
563 }
564 
565 
566 /**
567  * @brief Increase the table capacity.
568  * @param capacity The new table capacity.
569  * @param ctx Execution context.
570  *
571  * No action will be taken if @c capacity is smaller
572  * than the current capacity.
573  */
574 T_CONCURRENTMAP
575 inline
576 void CONCURRENTMAP::reserve (size_type capacity,
577  const Context_t& ctx
578  /*= Updater_t::defaultContext()*/)
579 {
580  return m_impl.reserve (capacity, ctx);
581 }
582 
583 
584 /**
585  * @brief Increase the table capacity.
586  * @param capacity The new table capacity.
587  *
588  * No action will be taken if @c capacity is smaller
589  * than the current capacity.
590  */
591 T_CONCURRENTMAP
592 inline
593 void CONCURRENTMAP::rehash (size_type capacity)
594 {
595  return reserve (capacity);
596 }
597 
598 
599 /**
600  * @brief Erase the table and change the capacity.
601  * @param capacity The new table capacity.
602  * @param ctx Execution context.
603  */
604 T_CONCURRENTMAP
605 inline
606 void CONCURRENTMAP::clear (size_t capacity,
607  const Context_t& ctx /*= Updater_t::defaultContext()*/)
608 {
609  m_impl.clear (capacity, ctx);
610 }
611 
612 
613 /**
614  * @brief Erase the table (don't change the capacity).
615  * @param ctx Execution context.
616  */
617 T_CONCURRENTMAP
618 inline
619 void CONCURRENTMAP::clear (const Context_t& ctx /*= Updater_t::defaultContext()*/)
620 {
621  m_impl.clear (ctx);
622 }
623 
624 
625 /**
626  * @brief Erase the table in-place.
627  *
628  * This method avoids allocating a new version of the table.
629  * However, it is not safe to use concurrently --- no other threads
630  * may be accessing the container at the same time, either for read
631  * or write.
632  */
633 T_CONCURRENTMAP
634 inline
635 void CONCURRENTMAP::forceClear()
636 {
637  m_impl.forceClear();
638 }
639 
640 
641 /**
642  * @brief Called when this thread is no longer referencing anything
643  * from this container.
644  * @param ctx Execution context.
645  */
646 T_CONCURRENTMAP
647 inline
648 void CONCURRENTMAP::quiescent (const Context_t& ctx)
649 {
650  return m_impl.quiescent (ctx);
651 }
652 
653 
654 /**
655  * @brief Swap this container with another.
656  * @param other The container with which to swap.
657  *
658  * This will also call swap on the Updater object; hence, the Updater
659  * object must also support swap. The Hasher and Matcher instances
660  * are NOT swapped.
661  *
662  * This operation is NOT thread-safe. No other threads may be accessing
663  * either container during this operation.
664  */
665 T_CONCURRENTMAP
666 void CONCURRENTMAP::swap (ConcurrentMap& other)
667 {
668  return m_impl.swap (other.m_impl);
669 }
670 
671 
672 /**
673  * @brief Access the Updater instance.
674  */
675 T_CONCURRENTMAP
676 inline
677 auto CONCURRENTMAP::updater() -> Updater_t&
678 {
679  return m_impl.updater();
680 }
681 
682 
683 /**
684  * @brief Convert an underlying key value to this type's key value.
685  * @param val The underlying key value.
686  */
687 T_CONCURRENTMAP
688 inline
689 auto CONCURRENTMAP::keyAsKey (val_t val) -> key_type
690 {
691  return CxxUtils::detail::UIntConv<key_type>::uintToVal (val);
692 }
693 
694 
695 /**
696  * @brief Convert this type's key value to an underlying key value.
697  * @param k The key.
698  */
699 T_CONCURRENTMAP
700 inline
701 auto CONCURRENTMAP::keyAsVal (key_type k) -> val_t
702 {
703  return CxxUtils::detail::UIntConv<key_type>::valToUInt (k);
704 }
705 
706 
707 /**
708  * @brief Convert an underlying mapped value to this type's mapped value.
709  * @param val The underlying mapped value.
710  */
711 T_CONCURRENTMAP
712 inline
713 auto CONCURRENTMAP::mappedAsMapped (val_t val) -> mapped_type
714 {
715  return CxxUtils::detail::UIntConv<mapped_type>::uintToVal (val);
716 }
717 
718 
719 /**
720  * @brief Convert this type's mapped value to an underlying mapped value.
721  * @param val The mapped value.
722  */
723 T_CONCURRENTMAP
724 inline
725 auto CONCURRENTMAP::mappedAsVal (mapped_type val) -> val_t
726 {
727  return CxxUtils::detail::UIntConv<mapped_type>::valToUInt (val);
728 }
729 
730 
731 /**
732  * @brief Do a lookup in the table.
733  * @param key The key to look up.
734  *
735  * Returns an iterator of the underlying map pointing at the found
736  * entry or end();
737  */
738 T_CONCURRENTMAP
739 auto CONCURRENTMAP::get (key_type key) const
740  -> typename Impl_t::const_iterator
741 {
742  val_t kval = keyAsVal (key);
743  size_t hash = m_impl.hasher() (kval);
744  return m_impl.get (kval, hash);
745 }
746 
747 
748 /**
749  * @brief Insert / overwrite an entry in the table.
750  * @param key The key of the new item to add.
751  * @param val The value of the new item to add.
752  * @param overwrite If true, allow overwriting an existing entry.
753  * @param ctx Execution context.
754  *
755  * The first element in the returned pair is an iterator referencing
756  * the added item. The second is a flag that is true if a new element
757  * was added.
758  */
759 T_CONCURRENTMAP
760 auto CONCURRENTMAP::put (key_type key,
761  mapped_type val,
762  bool overwrite /*= true*/,
763  const Context_t& ctx /*= Updater_t::defaultContext()*/)
764  -> std::pair<const_iterator, bool>
765 {
766  val_t kval = keyAsVal (key);
767  size_t hash = m_impl.hasher() (kval);
768  auto [it, flag] = m_impl.put (kval, hash,
769  mappedAsVal (val),
770  overwrite, ctx);
771  return std::make_pair (const_iterator (it), flag);
772 }
773 
774 
775 /**
776  * @brief Insert / overwrite an entry in the table, with external locking.
777  * @param lock The lock object returned from lock().
778  * @param key The key of the new item to add.
779  * @param val The value of the new item to add.
780  * @param overwrite If true, allow overwriting an existing entry.
781  * @param ctx Execution context.
782  *
783  * The first element in the returned pair is an iterator referencing
784  * the added item. The second is a flag that is true if a new element
785  * was added.
786  */
787 T_CONCURRENTMAP
788 auto CONCURRENTMAP::put (const Lock_t& lock,
789  key_type key,
790  mapped_type val,
791  bool overwrite /*= true*/,
792  const Context_t& ctx /*= Updater_t::defaultContext()*/)
793  -> std::pair<const_iterator, bool>
794 {
795  val_t kval = keyAsVal (key);
796  size_t hash = m_impl.hasher() (kval);
797  auto [it, flag] = m_impl.put (lock, kval, hash,
798  mappedAsVal (val),
799  overwrite, ctx);
800  return std::make_pair (const_iterator (it), flag);
801 }
802 
803 
804 #undef T_CONCURRENTMAP
805 #undef CONCURRENTMAP
806 
807 
808 } // namespace CxxUtils