对轻量级,线程安全的调度程序的建议

时间:2011-01-17 12:51:01

标签: c# multithreading thread-safety

我正在尝试为轻量级线程(光纤)编写循环调度程序。它必须扩展以处理尽可能多的同时调度的光纤。我还需要能够从运行循环所在的线程之外的线程中调度光纤,并且最好还从任意线程中取消调度它们(尽管我只能从运行循环中取消调度它们)。

我目前的想法是建立一个循环的双向链表,其中每个光纤都是一个节点,调度程序保存对当前节点的引用。这就是我到目前为止所做的:

using Interlocked = System.Threading.Interlocked;

public class Thread {
    internal Future current_fiber;

    public void RunLoop () {
        while (true) {
            var fiber = current_fiber;
            if (fiber == null) { 
                // block the thread until a fiber is scheduled
                continue;
            }
            if (fiber.Fulfilled)
                fiber.Unschedule ();
            else
                fiber.Resume ();

            //if (current_fiber == fiber) current_fiber = fiber.next;
            Interlocked.CompareExchange<Future> (ref current_fiber, fiber.next, fiber);      
        }
    }
}

public abstract class Future {
    public bool Fulfilled { get; protected set; }
    internal Future previous, next;

    // this must be thread-safe
    // it inserts this node before thread.current_fiber
    // (getting the exact position doesn't matter, as long as the
    //  chosen nodes haven't been unscheduled)
    public void Schedule (Thread thread) {
        next = this; // maintain circularity, even if this is the only node
        previous = this;
    try_again:
        var current = Interlocked.CompareExchange<Future> (ref thread.current_fiber, this, null);
        if (current == null)
            return;

        var target = current.previous;
        while (target == null) {
            // current was unscheduled; negotiate for new current_fiber
            var potential = current.next;
            var actual = Interlocked.CompareExchange<Future> (ref thread.current_fiber, potential, current);
            current = (actual == current? potential : actual);
            if (current == null)
                goto try_again;
            target = current.previous;
        }
        // I would lock "current" and "target" at this point.
        // How can I do this w/o risk of deadlock?
        next = current;
        previous = target;
        target.next = this;
        current.previous = this;
    }

    // this would ideally be thread-safe
    public void Unschedule () {
        var prev = previous;
        if (prev == null) {
            // already unscheduled
            return;
        }
        previous = null;
        if (next == this) {
            next = null;
            return;
        }
        // Again, I would lock "prev" and "next" here
        // How can I do this w/o risk of deadlock?            
        prev.next = next;
        next.previous = prev;
    }

    public abstract void Resume ();
}

正如您所看到的,我的疑点是我无法确保锁定的顺序,因此我无法锁定多个节点而不会有死锁的风险。或者我可以吗?我不想对Thread对象进行全局锁定,因为锁争用的数量是极端的。另外,我并不特别关心插入位置,所以如果我单独锁定每个节点,那么Schedule()可以使用类似Monitor.TryEnter的东西,只需继续遍历列表,直到找到解锁的节点。

总的来说,只要符合我提到的要求,我就不会投入任何特定的实施。任何想法将不胜感激。谢谢!

编辑 - 评论表明,人们认为我在谈论的是winapi纤维(我不是)。简而言之,我想要做的就是安排一些代码在单个线程上一个接一个地运行。它类似于TPL / Async CTP,但AFIK不保证在同一个线程上继续,除非它恰好是UI线程。我愿意接受有关如何实施上述内容的其他建议,但请不要只说“不要使用光纤”。

4 个答案:

答案 0 :(得分:2)

使用任务并行库。

创建自定义TaskScheduler,如MSDN所示。在自定义任务计划程序中,如果只需要一个线程,则只能有一个线程。

如果您想阻止它内联计划任务,请覆盖protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)并返回false

答案 1 :(得分:0)

不要将Fibers与.NET一起使用,请阅读Joe Duffy关于此主题的任何内容。使用TPL代替正确实现用户模式调度程序。

答案 2 :(得分:0)

我猜测不需要在单个线程上运行,一次只有一个任务运行完成。如果是这种情况,请下载TPL samples。查看使用MaximumConcurrencyLevelTask​​Scheduler,MaximumConcurrency为1。

答案 3 :(得分:0)

您应该能够使用简单的lock-free message queue对期货进行排队。

不是保留循环数据结构,而是在调用Resume()之前将Future拉出队列,并在完成时附加它(如果还有更多工作要做)。