from threading import Lock


class Budget:
    def __init__(self, limit):
        self.limit, self.spent = limit, 0
        self.pending, self.closed = {}, set()
        self.lock = Lock()

    def reserve(self, key, ceiling):
        with self.lock:
            if ceiling <= 0 or key in self.pending or key in self.closed:
                return False
            available = self.limit - self.spent - sum(self.pending.values())
            if ceiling > available:
                return False
            self.pending[key] = ceiling
            return True

    def settle(self, key, actual):
        with self.lock:
            ceiling = self.pending[key]
            if not 0 <= actual <= ceiling:
                raise ValueError("实际用量超出约定上限，需异常清算")
            self.spent += actual
            del self.pending[key]
            self.closed.add(key)


budget = Budget(20)
assert budget.reserve("call-1", 15)
assert not budget.reserve("call-2", 10)
budget.settle("call-1", 8)
assert budget.reserve("call-2", 10)
assert not budget.reserve("call-1", 1)
assert budget.spent + sum(budget.pending.values()) <= budget.limit
print("并发额度先预留，再结算，操作编号不能复用")
