C# 线程会合实例

有这样一个题目:四个线程t1,t2,t3,t4,向4个文件中写入数据,要求:t1只能写入“1”,t2只能写入“2”,t3只能写入“3”,t4只能写入“4”,对4个文件A,B,C,D写入如下内容:

  • A:123412341234.....
  • B:234123412341....
  • C:341234123412....
  • D:412341234123....

简单分析一下,对于A文件,t1写入“1”后,我们希望通知t2来写“2”,并且t1前往D文件等着去写“1”,以此类推。

1. 通过等待句柄实现

显然可以用等待句柄来实现,通知t2来写“2”相当于在一个等待句柄上调用 Set 方法,等待在D文件上写“1”相当于在另一等待句柄上调用了 WaitOne 方法,下边是利用4个 AutoResetEvent 来实现它:

class Program
{
    private static List<StreamWriter> _sws;

    private static List<AutoResetEvent> _whs;

    private static void Main(string[] args)
    {
        var fileNames = new List<string> {"A", "B", "C", "D"}; 

        // 创建或清空文件
        fileNames.ForEach(name =>
        {
            if (!File.Exists(name))
                File.Create(name);
            else
            {
                using (var sw = new StreamWriter(name))
                    sw.Write("");
            }
        });

        _sws = fileNames.Select(File.AppendText).ToList();

        // 为每个文件写入建立一个等待句柄
        _whs = fileNames.Select(name => new AutoResetEvent(false)).ToList();

        // 创建并启4个线程执行任务
        var threads = new List<Thread> {
            new Thread(() => Work(1)), new Thread(() => Work(2)), new Thread(() => Work(3)), new Thread(() => Work(4))
        };
        threads.ForEach(t => t.Start());

        // 等待线程结束并关闭 StreamWrite
        threads.ForEach(t => t.Join());
        Console.WriteLine("任务完成!");
        _sws.ForEach(sw => sw.Close());
    }

    static void Work(int threadIndex)
    {
        var next = threadIndex - 1;
        // 为让程序能结束,就打印100次
        for (int i = 0; i < 100; i++)
        {
            var wh = _whs[next];
            var sw = _sws[next];
            lock (sw)
            {
                sw.Write(threadIndex);
            }
            next = (next - 1) < 0 ? 3 : next - 1;

            WaitHandle.SignalAndWait(wh, _whs[next]);  //在wh上发信号,并在下一个等待句柄上等待执行
        }
    }
}

上述例子中我们创建了4个线程来分别打印1,2,3,4,并且为每个文件的写入创建了4个等待句柄来进行信号通信。最后主线程在等待所有线程结束后,关闭文件流。为让程序能正常结束,在 Work 方法中就只循环写100次。

以t1(列表中第一个线程)为例,在A文件中打印1后,调用 WaitHandle.SignalAndWait(wh,wh[next]),即在wh上发信号通知可以接着写入了,并在下一个等待句柄上等待写入信号。

关于 SinalAndWait 的可以参见 Thread in C# 或者对应的 中文翻译 。

2. 通过 Barrier 类实现

除了通过等待句柄可以实现题目要求外,同样可以通过 Wait 和 Pulse 来实现。如果是FrameWork 4.0或更高的版本,可以通过 Barrier 类(它是建立在 Wait / Pulse 和自旋锁基础上的)更简单的实现这个题目。

class Program
{
    private static Barrier _barrier;

    private static void Main(string[] args)
    {
        var fileNames = new List<string> { "A", "B", "C", "D" };

        // 创建或清空文件
        fileNames.ForEach(name =>
        {
            if (!File.Exists(name))
                File.Create(name);
            else
            {
                using (var sw = new StreamWriter(name))
                    sw.Write("");
            }
        });

        // 在_barrier上调用SignalAndWait的线程会被阻塞直到这个方法被调用4次
        _barrier = new Barrier(4);

        _sws = fileNames.Select(File.AppendText).ToList();

        // 创建并启4个线程执行任务
        var threads = new List<Thread> {
            new Thread(() => Work(1)), new Thread(() => Work(2)), new Thread(() => Work(3)), new Thread(() => Work(4))
        };
        threads.ForEach(t => t.Start());

        // 等待线程结束并关闭 StreamWrite
        threads.ForEach(t => t.Join());
        Console.WriteLine("任务完成!");
        _sws.ForEach(sw => sw.Close());
    }

    static void Work(int threadIndex)
    {
        var next = threadIndex - 1;
        for (int i = 0; i < 100; i++)
        {
            var sw = _sws[next];
            lock (sw)
            {
                sw.Write(threadIndex);
            }
            _barrier.SignalAndWait();
            next = (next - 1) < 0 ? 3 : next - 1;
        }
    }
}

使用了一个 Barrier 类来替代4个等待句柄,线程调用 SignalAndWait 后会阻塞,直到这个方法被调用4次。在这个例子中意味着4个线程总是在同步进行着打印,下图可以很好的解释 Barrier 类:

关于 Barrier 类,可以参见 Thread in C# 或者对应的 中文翻译 。

时间: 2025-01-17 12:25:46

C# 线程会合实例的相关文章

JStorm/Strom配置executors和tasks(线程和实例)

注意:JStorm马上融合到Strom内核了,这意味着以后没有Strom了.不过Twitter 对外宣讲了他们的Heron系统, JStorm作者的博文分析:深度分析Twitter Heron[http://www.longda.us/?p=529] Twitter Heron[http://www.longda.us/?p=529] 配置executors和tasks(线程和实例) 请始终记得标题:executors和tasks(线程和实例),executors代表线程概念,tasks代表sp

Java多线程之简单的线程同步实例

数据类: package Thread.MyCommon; public class Data { public int num = 0; public synchronized int getEven() { ++num; Thread.yield();//让另外线程先执行,加大测试效果几率 ++num; return num; } } 线程类: package Thread.MyCommon; public class myThread implements Runnable { priva

java线程池实例

目的         了解线程池的知识后,写个线程池实例,熟悉多线程开发,建议看jdk线程池源码,跟大师比,才知道差距啊O(∩_∩)O 线程池类 1 package thread.pool2; 2 3 import java.util.LinkedList; 4 5 public class ThreadPool { 6 //最大线程数 7 private int maxCapacity; 8 //初始线程数 9 private int initCapacity; 10 //当前线程数 11 p

asp.net 线程创建实例

线程在ASP.NET 中的使用非常广泛,也非常重要.同样,asp.net中创建一个线程的实现方法也非常简单,只需将其声明并为其提供线程起始点处的方法委托即可实现.创建新的线程时,需要使用 Thread 类,Thread 类具有接受一个 ThreadStart 委托或 ParameterizedThreadStart 委托的构造函数.该委托包装了调用 Start 方法时由新线程调用的方法.创建了Thread类 的对象之后,线程对象已存在并已配置,但并未创建实际的线程,只有在调用Start()方法后

java中volatile不能保证线程安全(实例讲解)

java中volatile不能保证线程安全(实例讲解) 转载  2017-09-04   作者:Think-007    我要评论 下面小编就为大家带来一篇java中volatile不能保证线程安全(实例讲解).小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 今天打了打代码研究了一下java的volatile关键字到底能不能保证线程安全,经过实践,volatile是不能保证线程安全的,它只是保证了数据的可见性,不会再缓存,每个线程都是从主存中读到的数据,而不是从缓存

Linux C++线程池实例

想做一个多线程服务器测试程序,因此参考了github的一些实例,然后自己动手写了类似来加深理解. 目前了解的线程池实现有2种思路: 第一种: 主进程创建一定数量的线程,并将其全部挂起,此时线程状态为idle,并将running态计数为0,等到任务可以执行了,就唤醒线程,此时线程状态为running,计数增加,如果计数达到最大线程数,就再创建一组空闲线程,等待新任务,上一组线程执行完退出,如此交替. 第二种: 采用生成者-消费者模式,主进程作为生成者,创建FIFO队列,在任务队列尾部添加任务,线程

线程池实例:使用Executors和ThreadPoolExecutor

线程池负责管理工作线程,包含一个等待执行的任务队列.线程池的任务队列是一个Runnable集合,工作线程负责从任务队列中取出并执行Runnable对象. java.util.concurrent.executors 提供了 java.util.concurrent.executor 接口的一个Java实现,可以创建线程池.下面是一个简单示例: 首先创建一个Runable 类: WorkerThread.java package com.journaldev.threadpool; public

linux线程同步实例

[Linux多线程]三个经典同步问题 - 神奕的专栏 - 博客频道 - CSDN.NET http://blog.csdn.net/lisonglisonglisong/article/details/45390227 Linux多进程多线程互斥同步例子_Linux编程_Linux公社-Linux系统门户网站 http://www.linuxidc.com/Linux/2013-01/78394.htm UNIX/Linux-线程控制(实例入门篇) - 菜鸟的自留地- 博客频道 - CSDN.N

windows下使用Critical Section和Mutex实现线程同步实例

利用critical section 和 Mutex两种不同的线程同步的方法实现生产者消费者问题.生产者线程要能够对一个计数器进行增的操作,并且将其输出在控制台上,消费者线程能够对这个计数器进行减的操作,并将其输出在控制台上.两种线程都共享一个计数器. 其中增.减计数器的数我设置为1-6随机. 测试两种方法的对比,用网上整理出的一张表如下 1.使用CriticalSection 方法时,有一个临界区cs 在将临界区传递给 InitializeCriticalSection 时(或者更准确地说,是