高效并发数据结构:等待计算结果或超时方案咨询
Hey there, let's break down your problem clearly: you have web requests holding a unique key, needing to fetch a corresponding computed result, block up to n seconds if the result isn't ready yet, and return a "not available" message on timeout. You want to avoid reinventing the wheel with existing, efficient concurrent patterns—great call, that's where the right data structures shine.
Core Requirements Recap
First, let's align on what we need from the solution:
- Map unique keys to their "pending/ready" result state
- Support multiple concurrent requests waiting on the same key's result
- Safe, efficient blocking with configurable timeout
- Prevent redundant computation for the same key (critical to avoid wasted resources)
1. Single-Machine: ConcurrentHashMap + Condition Variables (Java)
If you're working in a single JVM environment, this is a classic, low-level but highly customizable approach:
- Use a
ConcurrentHashMap<String, ResultHolder>where the key is your unique request key, andResultHolderis a custom class holding:- The computed result (nullable until ready)
- A
LockandConditionobject to manage waiting threads - A state flag (e.g.,
PENDING,READY,TIMED_OUT)
- When a request comes in:
- Use
map.putIfAbsent(key, new ResultHolder())to atomically create a holder if it doesn't exist (prevents duplicate computation triggers) - Acquire the lock on the
ResultHolder - Wait on the
Conditionwith a timeout ofnseconds - On wakeup, check the state:
- If
READY: return the result - If
PENDING/TIMED_OUT: return "暂未获取到结果"
- If
- Use
- When the computation for a key finishes:
- Fetch the
ResultHolderfrom the map - Acquire the lock, update the result and state to
READY - Call
condition.signalAll()to wake all waiting requests
- Fetch the
Pro Tip: Add cleanup logic for timed-out or completed keys to avoid memory bloat (e.g., remove entries from the map after result is fetched or timeout expires).
2. Single-Machine: Guava LoadingCache (Java)
If you want a battle-tested, out-of-the-box solution that avoids rolling your own synchronization, Google Guava's LoadingCache is perfect:
- Configure the cache with a maximum size (to limit memory usage) and a custom loader that triggers the computation for a key if it's not present
- Use
loadingCache.get(key, n, TimeUnit.SECONDS)—this method will block until the result is ready or the timeout hits - The cache automatically handles concurrent requests for the same key: only one computation will run, and all waiting requests get the result once it's done
- You can also set expiration policies to automatically evict old results
Example snippet to illustrate the pattern:
LoadingCache<String, ComputedResult> resultCache = CacheBuilder.newBuilder() .maximumSize(1000) .build(new CacheLoader<String, ComputedResult>() { @Override public ComputedResult load(String key) throws Exception { // Trigger your computation logic here return computeResultForKey(key); } }); // In your web request handler: try { ComputedResult result = resultCache.get(key, n, TimeUnit.SECONDS); return result; } catch (TimeoutException e) { return "暂未获取到结果"; } catch (ExecutionException e) { // Handle computation errors }
3. Distributed Scenario: Redis Pub/Sub + Key Expiration
If your system runs across multiple servers (distributed), a local data structure won't work. Redis provides a clean solution:
- When a request comes in:
- Check if the key exists in Redis with
GET key—if yes, return the result - If not, subscribe to a dedicated channel for that key (e.g.,
result:{key}) - Wait up to
nseconds for a message on the channel; if no message arrives, unsubscribe and return "暂未获取到结果"
- Check if the key exists in Redis with
- When computation finishes:
- Store the result in Redis with
SET key result EX <expiry-time>(to auto-clean old results) - Publish the result to the channel
result:{key}to notify all waiting requests
- Store the result in Redis with
Bonus: Use Redis' BLPOP (blocking list pop) as an alternative to pub/sub—store pending result notifications in a list, and have requests block on popping from the list with a timeout.
Key Considerations to Avoid Pitfalls
- Redundant Computation: Always use atomic operations (like
putIfAbsentin Java, or RedisSETNX) to ensure only one computation runs per key - Memory/Disk Cleanup: Configure expiration for cached results to prevent resource leaks
- Timeout Precision: Be aware that some blocking mechanisms might have minor delays (e.g., OS thread scheduling), so adjust timeout values if strict precision is needed
- Error Handling: Don't forget to handle cases where the computation fails—you'll want to notify waiting requests or clean up the pending state
内容的提问来源于stack exchange,提问作者Mannie

