生产者消费者模式及简单的运用场景
先考虑一个问题:服务端接受多个客户端提交的视频文件进行转码的操作,应该怎么设计?
由于转码比较花费时间,所以我们排除同步的想法。而转码需要用到的外部软件(exe文件),不能同时被多个线程用到,所以我们排除为每一个客户端提交新建一个线程进行转码的想法。
于是我们想到了静态加锁和队列。静态加锁有个缺点,稍后再提。当我们选择了队列,就选择了生产者消费者模式。
其流程图:
有流程图我们可以知道,生产者不关心数据什么时候被处理,消费者不关心数据什么时候产生,实现了解耦,也解决了阻塞。
还有一个比较典型的例子便是日志的记录,多线程产生日志,但写日志由于文件独占,不能多线程来写,于是我们就可以把线程压入队列,由日志线程来读取队列数据,完成写日志的操作。下面是一个简单的实现:
public class Log { private static ConcurrentQueue<LogMessage> msgs = new ConcurrentQueue<LogMessage>(); public static void WriteLog(string msg) { msgs.Enqueue(new LogMessage(msg)); } public static void Start() { Task.Factory.StartNew(() => { while (true) { while (msgs.TryDequeue(out LogMessage msg)) { using (StreamWriter sw = new StreamWriter(msg.LogFile, true)) { sw.WriteLine(msg.Message); } } Thread.Sleep(1000); } }); } }
这个是写日志的类
class LogMessage { public string Message { get; set; } public string LogFile { get; set; } public LogMessage(string msg) { this.Message = $"{DateTime.Now.ToString("HH:mm:ss ")} {msg}"; string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log", DateTime.Now.ToString("yyyy-MM")); if (!Directory.Exists(path)) Directory.CreateDirectory(path); this.LogFile = Path.Combine(path, DateTime.Now.ToString("dd") + ".log"); } }
这个是日志结构。包括产生日志的时间和写日志的日志文件。可以实现23:59产生的日志写到当天的文件夹中。
日志工具类的调用也非常简单,直接调用静态方法WriteLog就行。
回到开头所说加锁的弊端:线程排队并不是在队列中,没有先后顺序的保证,牵扯到严格顺序时就会有问题,比如写日志,socket数据接受等。
模式的应用场景:处理数据比较消耗时间,线程独占,生产数据不需要即时的反馈等。
例子的不足:
1.省略掉了缓冲区,使得生产者和消费者并不是完全解绑。改进:用一个独立的数据结构来放置数据,可以是缓存、文件、数据库,实现仅依赖于数据格式的解绑。
2.程序结束时,我们不能保证缓冲区数据是否全部处理完。改进:生产日志时,写文件/数据库,处理数据后,对处理过的数据进行标记,程序异常结束也没问题,下次重启先加载未处理数据,再一次展现单纯加锁的弊端。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)