多进程、多线程、同步写
Windows编程中不建议创建进程(进程创建的开销不容忽视),若需要大量创建进程,最好切换到Linux系统;
Windows偏向多线程,大量面对资源争抢与同步的问题。
在面向多核的服务器端编程中,需要习惯多进程而非多线程。(在CPU多核情况下,多线程在性能上不如多进程);Linux偏向进程间通信的方法。
进程与线程比较
比较 | 进程 | 线程 |
---|---|---|
定义 | 调度:进程是资源的分配和调度的独立单元,拥有完整的虚拟地址空间; 并发性:进程之间可以并发执行,安全性高; 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销大; |
调度:线程是CPU调度的基本单元,不拥有系统资源,但可以访问隶属于进程的资源; 并发性:同一个进程的多个线程之间也可并发执行,维护成本高; 系统开销:创建或撤消线程时的开销小; |
通信定义 | 进程通信一般指不同进程间的线程进行通讯。 | 线程通信一般指同一进程内的线程进行通讯。 |
通信方式 | IPC(Inter Process Communication)协程 管道(Pipe) 多用于父子关系的进程;无名管道用来在操作系统进程间进行较大数据量通信;有名管道用来使服务器通过网络与多个用户交互; 信号(Signal); 消息队列 用来在客户端修改了共享内存后通知服务器读取; 共享内存 用来传递数据; 信号量(Semaphore) 用于生产者/消费者,对性能要求比较高,避免在内核模式和用户模式下频繁相互切换线程; 套接字(Socket); |
全局变量、自定义消息响应等。 |
同步机制 | Mutex(互斥)、Semaphore等可以跨进程使用。 | 临界区、互斥、信号量、事件等。 |
同步方法 | 阻塞:Sleep(使调用线程阻塞)、Join、EndInvoke(使另外一个线程阻塞); 加锁: Mutex ,Semaphore ,Event ,互斥锁、信号量和句柄,可以在不同的进程间实现线程同步;由于都需要进入内核模式,所以最慢。 |
优先使用线程安全的类型(System.Collections.Concurrent),比如:ConcurrentDictionary。 Interlocked ,为多线程共享的基础类型(int、double等)变量提供线程安全的原子操作;速度最快。lock(object) ,允许同一时间只有一个线程执行;常规应用。Monitors ,lock的内部实现;ReaderWriterLockSlim ,允许同一时间有多个线程可以执行读操作,或者只有一个有排它锁的线程执行写操作。速度慢于lock,常用于线程安全的缓存场景。 |
private static readonly object obj = new object();
同步线程访问共享资源时,提供一个专用对象加锁,且专锁专用。为避免死锁或锁争用,锁对象避免使用
this
(会滥用),Type instance
(实例可能由typeof或反射获得),string
(字符串会被截留),保留锁的时间也要尽可能短。
同步方法
- 互斥/加锁,目的是保证临界区代码操作的“原子性”;
- 信号量操作,目的是保证多个线程按照一定顺序执行。
进程之间的同步方式比线程间的同步方式选择小。能用多进程(考虑程序稳定性)方便的解决问题就不要使用多线程。需要频繁创建销毁的(Web服务器)、需要进行大量计算的(图像处理、算法处理)、强相关的处理、多核分布的场景等优先使用线程。
同步机制
同步机制四原则:空闲让进、忙则等待、有限等待、让权等待。进程之间的同步方式比线程间的同步方式选择小。能用多进程(考虑程序稳定性)方便的解决问题就不要使用多线程。需要频繁创建销毁的(Web服务器)、需要进行大量计算的(图像处理、算法处理)、强相关的处理、多核分布的场景等优先使用线程。
Interlocked
在线程里访问共享资源。
static int usingResource = 0;
static void UseResource()
{
// 资源未占用,当前线程可用,加锁
if (0 == Interlocked.Exchange(ref usingResource, 1))
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
// TODO
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
// 释放锁
Interlocked.Exchange(ref usingResource, 0);
}
else // 资源被占用,等待解锁,可下次尝试再调用
{
Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name);
}
}
Monitors
Monitor.Enter(usingResource);
// TODO
Monitor.Exit(usingResource);
ReaderWriterLockSlim
在需要对资源读写的线程安全中,如果这个资源的读取频率高,写入频率相对比较低,则可以使用此种锁机制来进一步提升性能,这种情况在一些需要线程安全的缓存场景特别常见。
private ReaderWriterLockSlim _lockSlim = new ReaderWriterLockSlim();
private void Read()
{
_lockSlim.EnterReadLock();
try
{
//具体实现
}
finally
{
_lockSlim.ExitReadLock();
}
}
private void Write(string value)
{
_lockSlim.EnterWriteLock();
try
{
//具体实现
}
finally
{
_lockSlim.ExitWriteLock();
}
}
Mutex
进程间同步。
private static Mutex _mut = new Mutex();
......
_mut.WaitOne();
// TODO
_mut.ReleaseMutex();
SemaphoreSlim
SemaphoreSlim是Semaphore的轻量型替代,只可在单个应用中使用。
Semaphore适合进程间同步。
private static SemaphoreSlim _semaphore = new SemaphoreSlim(0, 3);
......
_semaphore.Wait();
try
{
// TODO
}
finally {
var semaphoreCount = _semaphore.Release();
}
Event
AutoResetEvent
ManualResetEvent
生产者/消费者
复杂多线程环境下同步写测试
static int _RunCnt = 1000;
static int _SumCount = 0;
static int _WriteCount = 0;
static int _FailedCount = 0;
static void Main(string[] args)
{
int m, n;
ThreadPool.GetMinThreads(out m, out n);
Console.WriteLine("WorkerThreads-Min:{0} CompletionPortThreads-Min:{1}", m, n);
ThreadPool.GetMaxThreads(out m, out n);
Console.WriteLine("WorkerThreads-Max:{0} CompletionPortThreads-Max:{1}", m, n);
// 往线程池里添加一个任务,迭代写入N个任务
_SumCount += _RunCnt;
ThreadPool.QueueUserWorkItem(obj =>
{
Parallel.For(0, _RunCnt, e =>
{
WriteInfo();
});
});
// 在新的线程里,添加N个任务
_SumCount += _RunCnt;
Task.Run(() =>
{
Parallel.For(0, _RunCnt, e =>
{
ThreadPool.QueueUserWorkItem(obj =>
{
WriteInfo();
});
});
});
// 添加N个任务到线程池
_SumCount += _RunCnt;
Parallel.For(0, _RunCnt, e =>
{
ThreadPool.QueueUserWorkItem(obj =>
{
WriteInfo();
});
});
// 在当前线程里,迭代写入N个任务
_SumCount += _RunCnt;
Parallel.For(0, _RunCnt, e =>
{
WriteInfo();
});
while (true)
{
Console.WriteLine("Sum Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", _SumCount, _WriteCount, _FailedCount);
Console.ReadKey();
}
}
private static void WriteInfo()
{
try
{
// Do Sth
_WriteCount++;
}
catch (Exception ex)
{
string str = ex.Message;
_FailedCount++;
}
finally
{
// Release
}
}