Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Multithreading 多线程修改集合_Multithreading_C# 4.0 - Fatal编程技术网

Multithreading 多线程修改集合

Multithreading 多线程修改集合,multithreading,c#-4.0,Multithreading,C# 4.0,我正在为学校的作业学习线程,我正在尝试获取两个线程来清空一个集合。到目前为止,我使用的代码抛出了一个异常,表示集合已被修改 首先,我在锁定的代码部分中有一个while循环,但是(当然;-)只有一个线程清空集合 Thread t1 = new Thread(() => { while (containers.Count > 0) { GeefContainer(); Thread.Sleep(150);

我正在为学校的作业学习线程,我正在尝试获取两个线程来清空一个集合。到目前为止,我使用的代码抛出了一个异常,表示集合已被修改

首先,我在锁定的代码部分中有一个while循环,但是(当然;-)只有一个线程清空集合

Thread t1 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(150);
        }});
t1.Name = "Kraan 1";
t1.Start();

Thread t2 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(130);
        }});
t2.Name = "Kraan 2";
t2.Start();
我的问题是,如何让线程轮流清空集合

class Program
{
    private static List<int> containers = new List<int>();

    static void Main(string[] args)
    {
        for (int i = 0; i < 100; i++)
        {
            containers.Add(i);
        }

        Thread t1 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
        t1.Name = "Kraan 1";
        t1.Start();

        Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
        t2.Name = "Kraan 2";
        t2.Start();

        Console.Write("Press any key to continue...");
        Console.Read();
    }

    static void GeefContainer()
    {
        lock (containers)
        {
            int containerNummer = containers.Count - 1;

            //Container container = containers[containerNummer];

            //Console.Write("Container {0} opgehaald... Overladen", containerNummer);
            Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);

            //Gevaarlijk, want methode aanroepen kan klappen
            containers.RemoveAt(containerNummer);
        }
    }
}
类程序
{
私有静态列表容器=新列表();
静态void Main(字符串[]参数)
{
对于(int i=0;i<100;i++)
{
容器。添加(i);
}
线程t1=新线程(()=>{foreach(容器中的int容器){GeefContainer();}});
t1.Name=“Kraan 1”;
t1.Start();
线程t2=新线程(()=>{foreach(容器中的int容器){GeefContainer();}});
t2.Name=“Kraan 2”;
t2.Start();
控制台。写入(“按任意键继续…”);
Console.Read();
}
静态void GeefContainer()
{
锁(集装箱)
{
int containernumer=containers.Count-1;
//Container Container=容器[containernumer];
//Write(“Container{0}opgehaald…Overladen”,containernumer);
WriteLine(“Schip:Container{0}gegegevenaan{1}”,containernumer,Thread.CurrentThread.Name);
//Gevaarlijk,想要一个新的方法
容器。移除(容器夏天);
}
}
}

如果您按照以下方式修改线程会怎么样?这样,两个线程都应该有时间对集合执行操作

Thread t1 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(150);
        }});
t1.Name = "Kraan 1";
t1.Start();

Thread t2 = new Thread(() => { 
        while (containers.Count > 0)
        {
            GeefContainer(); 
            Thread.Sleep(130);
        }});
t2.Name = "Kraan 2";
t2.Start();

首先,将thred定义更改如下:

new Thread(() => { while(containers.Count>0) { GeefContainer(); } });
然后,按如下方式重写GeefContainer(),以避免出现异常:

static void GeefContainer()
{
    lock (containers)
    {
        int containerNummer = containers.Count - 1;

        if(containerNummer>=0) 
        {
            //Container container = containers[containerNummer];

            //Console.Write("Container {0} opgehaald... Overladen", containerNummer);
            Console.WriteLine("Schip: Container {0} gegeven aan {1}", containerNummer, Thread.CurrentThread.Name);

            //Gevaarlijk, want methode aanroepen kan klappen
            containers.RemoveAt(containerNummer);
        }
    }
}

我假定不允许您使用System.collections.Concurrent命名空间中的任何ThreadSafe集合

在检查是否还有剩余条目时,您需要以独占方式访问containers集合。但是,您不希望一个线程在释放其锁之前获取独占控制权来删除所有条目。可以使用Monitor.Pulse允许等待锁定容器的其他线程“先走”。尝试以下GeefContainers的实现:

static void GeefContainer()
{
    lock (containers)
    {
        while (containers.Any()) // using linq, similar to: while(container.Count > 0)
        {
            containers.RemoveAt(0); // remove the first element

            // allow other threads to take control
            Monitor.Pulse(containers); // http://msdn.microsoft.com/en-us/library/system.threading.monitor.pulse.aspx
                            // Wait for a pulse from the other thread
                            Monitor.Wait(container);
        }
    }
}
哦,从以下位置删除循环逻辑:

Thread t2 = new Thread(() => { foreach (int container in containers) { GeefContainer(); } });
只需调用GeefContainer就足够了

这可以通过以下方式可视化:

  • 线程1获得对“集合”的锁定
  • 线程2被阻止,因为它正在等待“集合”的独占锁
  • 线程1从“集合”中删除一个条目
  • 线程1释放其对“集合”的锁,并尝试获得新的独占锁
  • 线程2获得对“集合”的锁定
  • 线程2从“集合”中删除一个条目
  • 线程2释放它对“集合”的锁,并尝试获得一个新的独占锁
  • 线程1获得对“集合”的锁定

etc

您看到的异常是由枚举器引发的。标准集合中的枚举数有检查以确保集合在枚举操作的中间没有被修改(通过<代码>前缀在您的情况下)。 因为您想让线程交替从集合中移除,所以您需要某种机制来允许线程相互发送信号。我们还必须小心,不要同时访问多个集合中的集合。即使是
Count
属性在没有同步的情况下也不能安全使用。这门课使信号传递变得非常容易。一个简单的
就足以实现同步。以下是我将如何做到这一点

public class Program
{
    public static void Main(string[] args)
    {
        var containers = new List<int>();

        for (int i = 0; i < 100; i++)
        {
            containers.Add(i);
        }

        var barrier = new Barrier(0);

        var t1 = new Thread(() => GeefContainers(containers, barrier));
        t1.Name = "Thread 1";
        t1.Start();

        var t2 = new Thread(() => GeefContainers(containers, barrier));
        t2.Name = "Thread 2";
        t2.Start();

        Console.Write("Press any key to continue...");
        Console.Read();
    }

    private static void GeefContainers(List<int> list, Barrier barrier)
    {
        barrier.AddParticipant();
        while (true)
        {
            lock (list)
            {
                if (list.Count > 0)
                {
                    list.RemoveAt(0);
                    Console.WriteLine(Thread.CurrentThread.Name + ": Count = " + list.Count.ToString());
                }
                else
                {
                    break;
                }
            }
            barrier.SignalAndWait();
        }
        barrier.RemoveParticipant();
    }

}

在上图中,
T1
T2
分别表示线程1和2上的删除操作<代码>(B)表示对
屏障的呼叫。信号和等待

谢谢,这有效!唯一的事情是;为什么它使用while循环而不使用foreach?这与获取枚举数有关吗?事实上,我猜如果您使用上面的
GeefContainer()
尝试foreach,您将不会再次看到异常。这是因为:在thread1使用数组中的100个元素启动foreach循环后,thread2会删除一些元素。然后在某个点上,循环继续,尽管数组已清空。然后
containerNummer
变为负值,并且
containers.removet(containerNummer)
引发异常。这就是我所看到的…谢谢你的洞察力和帮助!我把这一点告诉了使用等待和脉冲解决方案的人,因为它消除了两个循环。我想投你一票,但我没有足够的分数去投。这接近我想要的。它与上面的解决方案非常相似,但由于thread.sleep的缘故,它在线程中来回穿梭,运行得非常好。谢谢您的帮助!我把这一点告诉了使用等待和脉冲解决方案的人,因为它消除了两个循环。我想投你一票,但我没有足够的分数去投。非常抱歉,是我犯了这个错误。不过,现在我修复了它,我注意到只有线程1清空了集合。也许我应该去某个地方睡觉?@imnotatumber.oh等等…0792588-对不起,我的错。您确实需要让线程等待来自另一个线程的脉冲。只需将Wait语句放在循环的底部,看看我读到的关于线程和Wait和Pulse方法的修改代码,它说的是同样的事情,尽管我犯了将Wait()放在Pulse()之前的错误。你拥有它的方式非常有效……除了一件小事:最后一个数字(数字99)不会显示出来。这是因为在线程切换发生的那一刻,被切换到的线程不再需要处理任何东西(至少,我是这么认为的),因此退出while循环