多进程、多线程、同步写

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(字符串会被截留),保留锁的时间也要尽可能短。

同步方法

  1. 互斥/加锁,目的是保证临界区代码操作的“原子性”;
  2. 信号量操作,目的是保证多个线程按照一定顺序执行。

进程之间的同步方式比线程间的同步方式选择小。能用多进程(考虑程序稳定性)方便的解决问题就不要使用多线程。需要频繁创建销毁的(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

生产者/消费者

BlockingCollection

复杂多线程环境下同步写测试

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
    }            
}

posted @ 2019-12-31 18:05  wesson2019  阅读(347)  评论(0编辑  收藏  举报