c#题例-2025-07-19 03:25:44
日期: 2025-07-19 分类: AI写作 17次阅读
当然可以!下面是一道**专家级别**的 C# 程序员逻辑面试题,涉及 **多线程、锁机制、死锁预防、异步编程模型** 和 **设计模式理解**,适合考察高级开发者的综合能力。
---
### 🧠 面试题:实现一个线程安全的缓存刷新机制
#### 背景描述:
你正在开发一个高性能的缓存服务,该服务支持自动刷新缓存项(基于过期时间),并且支持多线程并发访问。你需要实现一个 `CacheService` 类,它具有以下功能:
- 支持存储键值对(`string key`, `string value`)。
- 每个缓存项都有一个过期时间(`TimeSpan`)。
- 缓存项在访问时如果已过期,则需要**异步刷新**(从数据库或远程服务重新加载),**但只允许一个线程发起刷新请求**(防击穿)。
- 在刷新期间,允许其他线程获取旧值(如果存在),直到刷新完成。
- 不允许出现死锁,并且要尽量减少锁的粒度以提高并发性能。
---
### ✅ 面试要求:
请实现以下接口:
```csharp
public interface ICacheService
{
Task
void Remove(string key);
}
```
其中:
- `GetOrRefreshAsync`:获取缓存值。如果缓存不存在或已过期,则触发刷新。
- `refreshFunc`:刷新缓存的函数,参数是 key,返回新的 value。
- `Remove`:手动移除缓存项。
---
### 🔍 考察点:
1. **线程安全与并发控制**:
- 使用合适的锁机制(如 `ConcurrentDictionary`, `SemaphoreSlim`, `AsyncLock` 等)。
- 避免缓存击穿(只有一个线程刷新)。
- 允许多个线程读取旧值(读写分离)。
2. **异步编程模型**:
- 正确使用 `Task`, `ConfigureAwait(false)`,避免死锁。
- 合理使用 `CancellationToken`。
3. **资源管理与性能优化**:
- 减少锁竞争,避免全局锁。
- 合理管理缓存项的生命周期和清理策略(可选扩展)。
4. **异常处理与健壮性**:
- 刷新失败时如何处理?是否保留旧值?
---
### 💡 提示(可引导候选人):
- 可使用 `ConcurrentDictionary
- 对每个 key 使用独立的 `SemaphoreSlim` 或 `AsyncKeyLock` 来控制刷新并发。
- 使用 `ValueTask
- 可以考虑使用 `MemoryCache` 或 `IMemoryCache` 作为底层实现(如果是 .NET Core/5+)。
---
### ✅ 示例代码(参考实现):
```csharp
public class CacheService : ICacheService
{
private class CacheItem
{
public string Value { get; set; }
public DateTime Expiration { get; set; }
public Task RefreshTask { get; set; }
}
private readonly ConcurrentDictionary
private readonly ConcurrentDictionary
public async Task
string key,
Func
TimeSpan expiration,
CancellationToken ct)
{
if (_cache.TryGetValue(key, out var item) && item.Expiration > DateTime.UtcNow)
{
return item.Value;
}
var gate = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
await gate.WaitAsync(ct).ConfigureAwait(false);
try
{
// 双重检查
if (_cache.TryGetValue(key, out item) && item.Expiration > DateTime.UtcNow)
{
return item.Value;
}
// 启动刷新任务
item = new CacheItem
{
RefreshTask = RefreshInternal(key, refreshFunc, expiration)
};
_cache[key] = item;
// 等待刷新完成
await item.RefreshTask.ConfigureAwait(false);
return item.Value;
}
finally
{
gate.Release();
}
}
private async Task
{
try
{
var newValue = await refreshFunc(key).ConfigureAwait(false);
_cache[key] = new CacheItem
{
Value = newValue,
Expiration = DateTime.UtcNow + expiration
};
return newValue;
}
catch
{
// 可选择保留旧值或标记为失败
return _cache.TryGetValue(key, out var old) ? old.Value : null;
}
}
public void Remove(string key)
{
_cache.TryRemove(key, out _);
_locks.TryRemove(key, out var gate);
gate?.Dispose();
}
}
```
---
### 🧪 延伸问题(可选):
- 如何处理缓存雪崩?
- 如果刷新函数非常耗时,如何避免影响主线程?
- 如何为缓存项添加滑动过期(Sliding Expiration)?
- 如何测试这个缓存服务的并发安全性?
---
如果你需要,我也可以提供单元测试示例或 Mock 测试场景。希望这道题能帮助你考察候选人的综合能力!需要其他类型的题目也可以告诉我。
除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
标签:AI写作
上一篇:无
精华推荐