简体   繁体   English

“固定”/“负载均衡”C#线程池?

[英]“Fixed” / “Load Balanced” C# thread pool?

I have a 3rd party component that is "expensive" to spin up. 我有一个“昂贵”的第三方组件。 This component is not thread safe. 该组件不是线程安全的。 Said component is hosted inside of a WCF service (for now), so... every time a call comes into the service I have to new up the component. 所述组件托管在WCF服务内(暂时),因此......每次呼叫进入服务时,我都必须新建组件。

What I'd like to do instead is have a pool of say 16 threads that each spin up their own copy of the component and have a mechanism to call the method and have it distributed to one of the 16 threads and have the value returned. 我想做的是拥有一个说16个线程的池,每个线程都会启动它们自己的组件副本,并且有一个机制来调用该方法并将其分配给16个线程中的一个并返回值。

So something simple like: 所以很简单:

var response = threadPool.CallMethod(param1, param2);

Its fine for the call to block until it gets a response as I need the response to proceed. 它可以阻止调用,直到它得到响应,因为我需要响应才能继续。

Any suggestions? 有什么建议? Maybe I'm overthinking it and a ConcurrentQueue that is serviced by 16 threads would do the job, but now sure how the method return value would get returned to the caller? 也许我正在过度思考它并且由16个线程提供服务的ConcurrentQueue可以完成这项工作,但现在确定方法返回值将如何返回给调用者?

WCF will already use the thread pool to manage its resources so if you add a layer of thread management on top of that it is only going to go badly. WCF已经使用线程池来管理它的资源,所以如果你在它之上添加一层线程管理,它只会变得非常糟糕。 Avoid doing that if possible as you will get contention on your service calls. 如果可能的话,请避免这样做,因为您会在服务电话上争用。

What I would do in your situation is just use a single ThreadLocal or thread static that would get initialized with your expensive object once. 我会做你的情况是只使用一个单一 的ThreadLocal或线程静态,将与你昂贵的对象,一旦得到初始化。 Thereafter it would be available to the thread pool thread. 此后它将可用于线程池线程。

That is assuming that your object is fine on an MTA thread; 假设您的对象在MTA线程上正常; I'm guessing it is from your post since it sounds like things are current working, but just slow. 我猜这是来自你的帖子,因为它听起来像当前的工作,但只是很慢。

There is the concern that too many objects get created and you use too much memory as the pool grows too large. 有人担心会创建太多的对象,并且当池变得太大时会占用太多内存。 However, see if this is the case in practice before doing anything else. 但是,在执行任何其他操作之前,请查看实际情况是否如此。 This is a very simple strategy to implement so easy to trial. 这是一个非常简单的策略,可以轻松实现。 Only get more complex if you really need to. 如果你真的需要,只会变得更复杂。

First and foremost, I agree with @briantyler: ThreadLocal<T> or thread static fields is probably what you want. 首先,我同意@briantyler: ThreadLocal<T>或线程静态字段可能就是你想要的。 You should go with that as a starting point and consider other options if it doesn't meet your needs. 如果它不符合您的需求,您应该将其作为起点并考虑其他选项。

A complicated but flexible alternative is a singleton object pool. 一个复杂但灵活的替代方案是单例对象池。 In its most simple form your pool type will look like this: 在最简单的形式中,您的池类型将如下所示:

public sealed class ObjectPool<T>
{
    private readonly ConcurrentQueue<T> __objects = new ConcurrentQueue<T>();
    private readonly Func<T> __factory;

    public ObjectPool(Func<T> factory)
    {
        __factory = factory;
    }

    public T Get()
    {
        T obj;
        return __objects.TryDequeue(out obj) ? obj : __factory();
    }

    public void Return(T obj)
    {
        __objects.Enqueue(obj);
    }
}

This doesn't seem awfully useful if you're thinking of type T in terms of primitive classes or structs (ie ObjectPool<MyComponent> ), as the pool does not have any threading controls built in. But you can substitute your type T for a Lazy<T> or Task<T> monad, and get exactly what you want. 如果您在原始类或结构(即ObjectPool<MyComponent> )方面考虑类型T ,这似乎并不是非常有用,因为池没有内置任何线程控件。但是您可以用类型T代替一个Lazy<T>Task<T> monad,并得到你想要的。

Pool initialisation: 池初始化:

Func<Task<MyComponent>> factory = () => Task.Run(() => new MyComponent());
ObjectPool<Task<MyComponent>> pool = new ObjectPool<Task<MyComponent>>(factory);

// "Pre-warm up" the pool with 16 concurrent tasks.
// This starts the tasks on the thread pool and
// returns immediately without blocking.
for (int i = 0; i < 16; i++) {
    pool.Return(pool.Get());
}

Usage: 用法:

// Get a pooled task or create a new one. The task may
// have already completed, in which case Result will
// be available immediately. If the task is still
// in flight, accessing its Result will block.
Task<MyComponent> task = pool.Get();

try
{
    MyComponent component = task.Result; // Alternatively you can "await task"

    // Do something with component.
}
finally
{
    pool.Return(task);
}

This method is more complex than maintaining your component in a ThreadLocal or thread static field, but if you need to do something fancy like limiting the number of pooled instances, the pool abstraction can be quite useful. 这个方法比在ThreadLocal或线程静态字段中维护组件更复杂,但是如果你需要做一些像限制池化实例数量这样的事情,那么池抽象可能非常有用。

EDIT 编辑

Basic "fixed set of X instances" pool implementation with a Get which blocks once the pool has been drained: 基本的“固定的X实例集”池实现,其中一旦池被耗尽, Get阻塞:

public sealed class ObjectPool<T>
{
    private readonly Queue<T> __objects;

    public ObjectPool(IEnumerable<T> items)
    {
        __objects = new Queue<T>(items);
    }

    public T Get()
    {
        lock (__objects)
        {
            while (__objects.Count == 0) {
                Monitor.Wait(__objects);
            }

            return __objects.Dequeue();
        }
    }

    public void Return(T obj)
    {
        lock (__objects)
        {
            __objects.Enqueue(obj);

            Monitor.Pulse(__objects);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM