diff --git a/CHANGELOG.md b/CHANGELOG.md
index a32cf759098803268a09ae32596a3385dbb43edb..291a6a613e11f096df942a52ded5c80886684cae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 * fixed circular import
 * added __len__ to PeekableQueue
+* fixed CallableGroup, added new class
 
 # v2.25.5
 
diff --git a/docs/coding/concurrent.rst b/docs/coding/concurrent.rst
index efe13cba8015d6d0c77ab64cd127ac7d0a3abc92..1c8f88e3fb3146765b3c5526fcb258d4fbdd59c9 100644
--- a/docs/coding/concurrent.rst
+++ b/docs/coding/concurrent.rst
@@ -9,27 +9,32 @@ DeferredValue
     :members:
 
 CallableGroup
-=============
+-------------
 
 .. autoclass:: satella.coding.concurrent.CallableGroup
     :members:
 
+.. autoclass:: satella.coding.concurrent.CancellableCallbackGroup
+    :members:
+
+
+CancellableCallback
+-------------------
+
+.. autoclass:: satella.coding.concurrent.CancellableCallback
+    :members:
+
 CallNoOftenThan
----------------
+===============
 
 .. autoclass:: satella.coding.concurrent.CallNoOftenThan
     :members:
 
 parallel_construct
-------------------
+==================
 
 .. autofunction:: satella.coding.concurrent.parallel_construct
 
-CancellableCallback
--------------------
-
-.. autoclass:: satella.coding.concurrent.CancellableCallback
-    :members:
 
 LockedDataset
 =============
diff --git a/satella/__init__.py b/satella/__init__.py
index 0522a19c3eae0152657cd48e273fd12350d284ae..abf44da4c2c18520c722effa9d727fcfcc463780 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.25.6a4'
+__version__ = '2.25.6a7'
diff --git a/satella/coding/concurrent/callablegroup.py b/satella/coding/concurrent/callablegroup.py
index 97192365af56af143222d9a1c054cfd79aa13125..cf59aee7a38b939d4a0cc9d21887a5dc48769d81 100644
--- a/satella/coding/concurrent/callablegroup.py
+++ b/satella/coding/concurrent/callablegroup.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
 import collections
 import copy
 import time
@@ -25,21 +26,23 @@ class CancellableCallback:
     :param callback_fun: function to call
 
     :ivar cancelled: whether this callback was cancelled (bool)
+    :ivar one_shotted: whether this callback was invoked (bool)
     """
-    __slots__ = 'cancelled', 'callback_fun'
+    __slots__ = 'cancelled', 'callback_fun', 'one_shotted'
 
     def __bool__(self) -> bool:
         return not self.cancelled
 
-    def __init__(self, callback_fun: tp.Callable):
+    def __init__(self, callback_fun: tp.Callable, one_shotted=False):
         self.callback_fun = callback_fun
         self.cancelled = False
+        self.one_shotted = one_shotted
 
     def __hash__(self):
         return hash(id(self))
 
-    def __eq__(self, other) -> bool:
-        return id(self) == id(other)
+    def __eq__(self, other: CancellableCallback) -> bool:
+        return id(self) == id(other) and self.one_shotted == other.one_shotted and self.cancelled == other.cancelled
 
     def __call__(self, *args, **kwargs):
         if not self.cancelled:
@@ -50,35 +53,43 @@ class CancellableCallback:
         Cancel this callback.
         """
         self.cancelled = True
-#
-# class CancellableCallbackGroup:
-#     """
-#
-#     A group of callbacks that you can simultaneously cancel.
-#
-#     Immutable. Also, hashable and __eq__able.
-#
-#     Regarding it's truth value - it's True if at least one callback has not been cancelled.
-#     """
-#
-#     def __init__(self, callbacks: tp.Iterable[CancellableCallback]):
-#         self.callbacks = list(callbacks)      # type: tp.List[CancellableCallback]
-#
-#     def cancel(self) -> None:
-#         """
-#         Cancel all of the callbacks.
-#         """
-#         for callback in self.callbacks:
-#             callback.cancel()
-#
-#     def __bool__(self) -> bool:
-#         return any(not callback.cancelled for callback in self.callbacks)
-#
-#     def __hash__(self):
-#         y = 0
-#         for callback in self.callbacks:
-#             y ^= hash(callback)
-#         return y
+
+
+def _callable_to_cancellablecallback(callback: NoArgCallable[[T], None], one_shot=False) -> CancellableCallback:
+    if isinstance(callback, NoArgCallable[[T], None]):
+        return CancellableCallback(callback)
+    elif isinstance(callback, CancellableCallback):
+        return callback
+
+
+class CancellableCallbackGroup:
+    """
+
+    A group of callbacks that you can simultaneously cancel.
+
+    Immutable. Also, hashable and __eq__able.
+
+    Regarding it's truth value - it's True if at least one callback has not been cancelled.
+    """
+
+    def __init__(self, callbacks: tp.Iterable[CancellableCallback]):
+        self.callbacks = list(callbacks)  # type: tp.List[CancellableCallback]
+
+    def cancel(self) -> None:
+        """
+        Cancel all of the callbacks.
+        """
+        for callback in self.callbacks:
+            callback.cancel()
+
+    def __bool__(self) -> bool:
+        return any(not callback.cancelled for callback in self.callbacks)
+
+    def __hash__(self):
+        y = 0
+        for callback in self.callbacks:
+            y ^= hash(callback)
+        return y
 
 
 class CallableGroup(tp.Generic[T]):
@@ -113,7 +124,7 @@ class CallableGroup(tp.Generic[T]):
         of them was cancelled
         """
         for clb in self.callables:
-             if clb:
+            if clb:
                 return True
         return False
 
@@ -126,12 +137,34 @@ class CallableGroup(tp.Generic[T]):
                 if isinstance(callable_, CancellableCallback) and not callable_:
                     dd.delete()
 
+    def add_many(self, callable_: tp.Sequence[tp.Union[NoArgCallable[T],
+                 tp.Tuple[NoArgCallable[T], bool]]]) -> CancellableCallbackGroup:
+        """
+        Add multiple callbacks
+
+        .. note:: Same callable can't be added twice. It will silently fail.
+                  Note that already called one-shots can be added twice
+
+        Basically every callback is cancellable.
+
+        :param callable_: sequence of either callables with will be registered as multiple-shots or a tuple of callback
+            (with an argument to register it as a one-shot)
+        :returns: CancellableCallbackGroup to cancel all of the callbacks
+        """
+
+        cancellable_callbacks = []
+        for clbl in callable_:
+            canc_callback = _callable_to_cancellablecallback(clbl)
+            self.add(canc_callback, one_shot=canc_callback.one_shotted)
+            return CancellableCallbackGroup(cancellable_callbacks)
+
     def add(self, callable_: tp.Union[CancellableCallback, NoArgCallable[T]],
             one_shot: bool = False) -> CancellableCallback:
         """
         Add a callable.
 
-        .. note:: Same callable can't be added twice. It will silently fail.
+        .. note:: Same callable can't be added twice. It will silently fail, and return an existing callbacks.
+                  Note that already called one-shots can be added twice
 
         Can be a :class:`~satella.coding.concurrent.CancellableCallback`, in that case
         method :meth:`~satella.coding.concurrent.CallableGroup.remove_cancelled` might
@@ -186,6 +219,8 @@ class CallableGroup(tp.Generic[T]):
 
             if not one_shot:
                 self.add(call, one_shot)
+            else:
+                call.one_shotted = True
 
         if self.gather:
             return results
diff --git a/tests/test_coding/test_concurrent.py b/tests/test_coding/test_concurrent.py
index 7dce062743df4fe7cfd83f7ddbd832333ac140f5..957002bc7119912d9bd7692f6ac5a758f9a72724 100644
--- a/tests/test_coding/test_concurrent.py
+++ b/tests/test_coding/test_concurrent.py
@@ -255,6 +255,30 @@ class TestConcurrent(unittest.TestCase):
         d = si.issue()
         self.assertGreater(d, c)
 
+    def test_callable_group(self):
+        cbgroup = CallableGroup()
+        a = {1:2, 3:4, 4:6}
+        def y():
+            nonlocal a
+            a[1] = 3
+
+        def z():
+            nonlocal a
+            a[3] += 4
+
+        def p():
+            nonlocal a
+            a[4] += 1
+
+        cbgroup.add_many(y, z)
+        cbgroup.add(CancellableCallback(p, one_shotted=False))
+        cbgroup()
+        self.assertEqual(a[1],3)
+        self.assertEqual(a[3], 4)
+        self.assertEqual(a[4], 7)
+        cbgroup()
+        self.assertEqual(a[4], 8)
+
     def test_peekable_queue_put_many(self):
         pkb = PeekableQueue()