• 软件测试技术
  • 软件测试博客
  • 软件测试视频
  • 开源软件测试技术
  • 软件测试论坛
  • 软件测试沙龙
  • 软件测试资料下载
  • 软件测试杂志
  • 软件测试人才招聘
    暂时没有公告

字号: | 推荐给好友 上一篇 | 下一篇

使用事件使线程同步

发布: 2007-7-01 20:40 | 作者: admin | 来源: | 查看: 25次 | 进入软件测试论坛讨论

领测软件测试网

简介

       当你在程序中使用多线程时,这经常会用到,而且这些线程需要同时访问同一个资源时,这个可能性很大,这时候你就需要用到线程同步技术。有些线程以只读方式访问资源,我们叫它ReadThread,而有些线程却需要对资源进行写入,我们叫它WriteThread,我们暂时先这样称呼。如果一个线程对资源的访问是读写方式,我们把它看作是WriteThread。下面是一个简单的windows应用程序,它有三个button,每个分别对应一种情况。你最好仔细看一下每个button按下时发生了什么。

第一种情况:没有同步

       好,现在我们有两个ReadThread并发运行,并访问同一个共享对象。另外,还有一个为这个共享对象设置一个有效值的WriteThread在ReadThread之前开始运行。在同一个代码段中我用Thread.Sleep来模拟线程的处理时间。

Thread t0 = new Thread(new ThreadStart(WriteThread));

Thread t1 = new Thread(new ThreadStart(ReadThread10));

Thread t2 = new Thread(new ThreadStart(ReadThread20));

t0.IsBackground = true;

t1.IsBackground = true;

t2.IsBackground = true;

t0.Start();

t1.Start();

t2.Start();

我们可以看到,两个ReadThread在WriteThread运行后立即运行了。下面是这些函数的代码。

public void ReadThread10()

{

    int a = 10;

          

    for(int y=0; y<5; y++)

    {

       string s = "ReadThread10";

       s += " # multiplier= ";

       s += Convert.ToString(a) + " # ";

       s += a * m_x;

       lstShow.Items.Add(s);

       Thread.Sleep(1000);

    }

}

 

public void ReadThread20()

{

    int a = 20;

 

    for(int y=0; y<5; y++)

    {

       string s = "ReadThread20";

       s += " # multiplier= ";

       s += Convert.ToString(a) + " # ";

       s += a * m_x;

       lstShow.Items.Add(s);

       Thread.Sleep(1000);

    }

}

 

public void WriteThread()

{

    Thread.Sleep(1000);

    m_x = 3;

}

当我们运行程序,我们会得到下面这样的效果。

 

 

呵,我们看到前两个值是错的,出错的原因是ReadThread在WriteThread执行完毕之前就执行了,这是我们不希望看到的,我们应该尽力避免这种情况的发生。

 

第二种情况:同步(一个WriteThread,多个ReadThread

现在我们来解决上一种情况中遇到的问题,我们将会用到一个线程同步类:ManualResetEvent,还象刚才一样启动一个WriteThread和两个ReadThread,唯一不同的是我们用了这些线程的安全版本。

 

Thread t0 = new Thread(new ThreadStart(SafeWriteThread));

Thread t1 = new Thread(new ThreadStart(SafeReadThread10));

Thread t2 = new Thread(new ThreadStart(SafeReadThread20));

t0.IsBackground=true;

t1.IsBackground=true;

t2.IsBackground=true;

t0.Start();

t1.Start();

t2.Start();

 

我们还需要加入一个ManualRestEvent对象。

 

public ManualResetEvent m_mre;

 

在构造函数中将其初始化。

 

m_mre = new ManualResetEvent(false);

现在我们来看一下SafeWriteThread函数:

public void SafeWriteThread()

{

    m_mre.Reset();

    WriteThread();

    m_mre.Set();

}

Reset函数将事件对象的状态设置成non-signaled,这意味着当前事件没有被设置,然后我们调用原来的WriteThread函数,实际上我们是跳过了Reset这一步,因为ManualResetEvent的构造函数事先已经将状态设置成了non-signaled,一旦WriteThread函数返回,我们就调用Set函数将对象状态设置成signaled的。

现在我们来看一下两个ReadThread函数:

public void SafeReadThread10()

{

   m_mre.WaitOne();

   ReadThread10();

}

 

public void SafeReadThread20()

{

   m_mre.WaitOne();

   ReadThread20();

}

WaitOne函数会被阻塞直到事件对象的状态被设置成signaled,在特定的时候,两个SafeReadThread都会被阻塞直到事件对象的状态被设置成signaled,SafeWriteThread只是在完成时设置事件对象状态。因此,我们能够确保读线塍一定在写线程执行完毕后才启动并读资源。现在,当我们运行程序时,会得到这样的结果:

第三种情况:同步(多个WriteThread,多个ReadThread

现在我们假设有两个WriteThread,ReadThread必须要等到所有的WriteThread完成之后才能启动。在实际当中,两个WriteThread很有可能会一起执行,但在这个例子中,我让他们按顺序执行,即:第二个必须在第一个执行完后才开始执行。这只是为了简单起见。我们给第二个WriteThread增加一个ManualResetEvent对象和一个ManualResetEvent对象数组。

public ManualResetEvent m_mreB;

public ManualResetEvent[] m_mre_array;

在构造函数中加入如下代码:

 

m_mreB = new ManualResetEvent(false);

m_mre_array = new ManualResetEvent[2];

m_mre_array[0] = m_mre;

m_mre_array[1] = m_mreB;

 

现在我们来看一下如何执行这四个线程:

 

Thread t0 = new Thread(new ThreadStart(SafeWriteThread));

Thread t0B = new Thread(new ThreadStart(SafeWriteThreadB));

Thread t1 = new Thread(new ThreadStart(SafeReadThread10B));

Thread t2 = new Thread(new ThreadStart(SafeReadThread20B));

t0.IsBackground = true;

t0B.IsBackground = true;

t1.IsBackground = true;

t2.IsBackground = true;

t0.Start();

t0B.Start();

t1.Start();

t2.Start();

 

正如你所见到的,现在有两个ReadThread和两个WriteThread,我们来看一下它们的实现:

 

public void SafeWriteThread()

{

    m_mre.Reset();

    WriteThread();

    m_mre.Set();

}

 

可以看到,SafeWriteThread和原来的一样。

 

public void SafeWriteThreadB()

{

    m_mreB.Reset();

    m_mreB.WaitOne();

    Thread.Sleep(1000);

    m_x += 3;

    m_mreB.Set();

}

现在你可以看到我们再第二个WriteThread中用了另一个事件对象,为了模拟,我们加入了WaitOne要等第一个线程完成,但正如前面所说,在实际当中,这并不一定是真的。

public void SafeReadThread10B()

{

    WaitHandle.WaitAll(m_mre_array);

    ReadThread10();

}

 

public void SafeReadThread20B()

{

    WaitHandle.WaitAll(m_mre_array);

    ReadThread20();

}

你可以看到我们在这里用了一个叫WaitAll的函数,它是类WaitHandle的一个静态成员函数。作用是阻塞此线程直到队列中每一个对象状态都被设置成了signaled。当我们运行程序可以得到下面的结果。

 

 

AutoResetEvent

还有一个类似的类叫AutoResetEvent,跟ManualResetEvent不同的是,当所有线程都释放以后,AutoResetEvent是自动重设成non-signaled的。我要提起这个类的目的就在与此。我们假设现在有几个线程都要访问同一个对象,我们不想让它们都同时访问,因此,当我们准备让一个线程访问的时候,我们要用一个对象来让其它的线程都在等,这个对象就是AutoResetEvent,现在一个线程执行时,同时发生的是,事件对象的状态会自动设置成non-signaled,这样一来其它的线程就要继续等,知道主线程或正在访问对象的线程将事件对象的状态设置成signaled。

下面是一个简单的Console程序来演示这个类的用法:

 

class Class1

{

    AutoResetEvent m_are;

    static void main(string[] args)

    {

       Class1 class1 = new Class1();

    }

 

    Class1()

    {

       m_are = new AutoResetEvent(false);

       Thread t1 = new Thread(new ThreadStart(abc));

       Thread t2 = new Thread(new ThreadStart(xyz));

       t1.Start();

       t2.Start();

       m_are.Set();

       Thread.Sleep(1000);

       m_are.Set();

    }

 

    void abc()

    {

       m_are.WaitOne();

       for(int i=0; i<5; i++)

       {

           Thread.Sleep(1000);

           Console.WriteLine("abc abc abc");

       }

    }

 

    void xyz()

    {

       m_are.WaitOne();

       for(int i=0; i<5; i++)

       {

           Thread.Sleep(1000);

           Console.WriteLine("xyz xyz xyz");

       }

    }

}

当我们运行程序可以得到下面的结果。

abc abc abc

abc abc abc

abc abc abc

xyz xyz xyz

abc abc abc

abc abc abc

xyz xyz xyz

xyz xyz xyz

xyz xyz xyz

xyz xyz xyz

 

总结

这篇文章虽然不能对线程同步以及事件类做全面的诠释,但我希望它能让你对线程同步有个更进一步的了解。

 

关于 Nishant Sivakumar

Nish is a real nice guy living in Trivandrum, India who has been coding since 1990, when he was 13 years old. He has been evangelizing Microsoft Technologies like VC++, C++/CLI and the .NET Framework for so long now that it@#s surprising how Microsoft hasn@#t offered him the post of Chief Technology Evangelist in Redmond!

He works for The Code Project and handles the Dundas MFC products Ultimate Toolbox, Ultimate Grid and Ultimate TCP/IP that are sold exclusively through The Code Project Storefront. He frequents the CP discussion forums when he is not coding, reading or writing. Nish hopes to visit at least three dozen countries before his human biological mechanism stops working (euphemism used to avoid the use of the d-word here), and regrets that he hasn@#t ever seen snow until now. Oh btw, it must be mentioned that normally Nish is not inclined to speak about himself in the 3rd person.

Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI.

 


文章来源于领测软件测试网 https://www.ltesting.net/


关于领测软件测试网 | 领测软件测试网合作伙伴 | 广告服务 | 投稿指南 | 联系我们 | 网站地图 | 友情链接
版权所有(C) 2003-2010 TestAge(领测软件测试网)|领测国际科技(北京)有限公司|软件测试工程师培训网 All Rights Reserved
北京市海淀区中关村南大街9号北京理工科技大厦1402室 京ICP备2023014753号-2
技术支持和业务联系:info@testage.com.cn 电话:010-51297073

软件测试 | 领测国际ISTQBISTQB官网TMMiTMMi认证国际软件测试工程师认证领测软件测试网