科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件实例解析C++/CLI线程之多任务

实例解析C++/CLI线程之多任务

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

从处理器的角度来看,线程是一个单独的执行流程,每个线程都有各自的寄存器及堆栈上下文。

作者:谢启东编译 来源:天极开发 2007年11月14日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
创建线程

  在例1中,主线程创建了两个其他的线程,这三个线程并行运行,并且未进行同步。在线程间并未共享数据,且当最后一个线程结束时,主进程也结束了。

  例1:

using namespace System;
using namespace System::Threading;

public ref class ThreadX
{
 int loopStart;
 int loopEnd;
 int dispFrequency;
 public:
  ThreadX(int startValue, int endValue, int frequency)
  {
   loopStart = startValue;
   loopEnd = endValue;
   dispFrequency = frequency;
  }

  /*1*/ void ThreadEntryPoint()
  {
   /*2*/ String^ threadName = Thread::CurrentThread->Name;

   for (int i = loopStart; i <= loopEnd; ++i)
   {
    if (i % dispFrequency == 0)
    {
     Console::WriteLine("{0}: i = {1,10}", threadName, i);
    }
   }
   Console::WriteLine("{0} thread terminating", threadName);
  }
};

int main()
{
 /*3a*/ ThreadX^ o1 = gcnew ThreadX(0, 1000000, 200000);
 /*3b*/ Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadX::ThreadEntryPoint));
 /*3c*/ t1->Name = "t1";

 /*4a*/ ThreadX^ o2 = gcnew ThreadX(-1000000, 0, 200000);
 /*4b*/ Thread^ t2 = gcnew Thread(gcnew ThreadStart(o2, &ThreadX::ThreadEntryPoint));
 /*4c*/ t2->Name = "t2";

 /*5*/ t1->Start();
 /*6*/ t2->Start();
 Console::WriteLine("Primary thread terminating");
}

  请看标记3a中第一条可执行语句,此处我们创建了一个用户自定义ThreadX类型的对象,这个类有一个构造函数、一个实例函数及三个字段。我们调用构造函数时,传递进一个开始、一个结束计数,及一个固定增量,其用于循环控制。

  在标记3b中,创建了一个库类型System::Thread的对象,它源自命名空间System::Threading,可用此对象来创建一个新的线程,但是,在线程可以工作之前,它必须要知道从哪开始执行,所以传递给Thread构造函数一个System::ThreadStart代理类型,其可支持不接受参数的任意函数,且没有返回值(作为一个代理,它可封装进多个函数,在本例中,只指定了一个)。在上面的代码中,指定了线程由执行对象o1的ThreadEntryPoint实例函数开始,一旦开始之后,这个线程将会执行下去直到函数结束。最后,在标记3c中,随意使用了一个名称,以设置它的Name属性。

  请看标记4a、4b及4c,第二个线程也一样,只不过设置了不同的循环控制及名称。
 
  眼下,已构造了两个线程对象,但并未创建新的线程,也就是说,这些线程处于未激活状态。为激活一个线程,必须调用Thread中的Start函数,见标记5与6。通过调用进入点函数,这个函数启动了一个新的执行线程(对一个已经激活的函数调用Start将导致一个ThreadStateException类型异常)。两个新的线程都各自显示出它们的名称,并在循环中定时地显示它们的进度,因为每个线程都执行其自身的实例函数,所以每个线程都有其自己的实例数据成员集。

  所有三个线程均写至标准输出,见插1,可看出线程中的输出是缠绕在一起的(当然,在后续的执行中,输出也可能有不同的顺序)。可见,主线程在其他两个线程启动之前就结束了,这证明了尽管主线程是其他线程的父类,但线程的生命期是无关的。虽然,例中使用的进入点函数无关紧要,但其可调用它可访问的任意其他函数。 插1:三个线程的缠绕输出

Primary thread terminating
t1: i = 0
t1: i = 200000
t1: i = 400000
t1: i = 600000
t2: i = -1000000
t2: i = -800000
t2: i = -600000
t2: i = -400000
t2: i = -200000
t2: i = 0
t2 thread terminating
t1: i = 800000
t1: i = 1000000
t1 thread terminating

  如果想让不同的线程由不同的进入点函数开始,只需简单地在同一或不同的类中,定义这些函数就行了(或作为非成员函数)。

  同步语句

  例2中的主程序有两个线程访问同一Point,其中一个不断地把Point的x与y坐标设置为一些新值,而另一个取回并显示这些值。即使两个线程由同一进入点函数开始执行,通过传递一个值给它们的构造函数,可使每个线程的行为都有所不同。

  例2:

using namespace System;
using namespace System::Threading;

public ref class Point
{
 int x;
 int y;
 public:

  //定义读写访问器

  property int X
  {
   int get() { return x; }
   void set(int val) { x = val; }
  }

  property int Y
  {
   int get() { return y; }
   void set(int val) { y = val; }
  }

  // ...

  void Move(int xor, int yor)
  {
   /*1a*/ Monitor::Enter(this);
   X = xor;
   Y = yor;
   /*1b*/ Monitor::Exit(this);
  }

  virtual bool Equals(Object^ obj) override
  {
   // ...

   if (GetType() == obj->GetType())
   {
    int xCopy1, xCopy2, yCopy1, yCopy2;
    Point^ p = static_cast<Point^>(obj);

    /*2a*/ Monitor::Enter(this);
    xCopy1 = X;
    xCopy2 = p->X;
    yCopy1 = Y;
    yCopy2 = p->Y;
    /*2b*/ Monitor::Exit(this);

    return (xCopy1 == xCopy2) && (yCopy1 == yCopy2);
   }

   return false;
  }

  virtual int GetHashCode() override
  {
   int xCopy;
   int yCopy;

   /*3a*/ Monitor::Enter(this);
   xCopy = X;
   yCopy = Y;
   /*3b*/ Monitor::Exit(this);
   return xCopy ^ (yCopy << 1);
  }

  virtual String^ ToString() override
  {
   int xCopy;
   int yCopy;

   /*4a*/ Monitor::Enter(this);
   xCopy = X;
   yCopy = Y;
   /*4b*/ Monitor::Exit(this);

   return String::Concat("(", xCopy, ",", yCopy, ")");
  }
};

public ref class ThreadY
{
 Point^ pnt;
 bool mover;
 public:
  ThreadY(bool isMover, Point^ p)
  {
   mover = isMover;
   pnt = p;
  }

  void StartUp()
  {
   if (mover)
   {
    for (int i = 1; i <= 10000000; ++i)
    {
     /*1*/ pnt->Move(i, i);
    }
   }
   else
   {
    for (int i = 1; i <= 10; ++i)
    {
     /*2*/ Console::WriteLine(pnt); // calls ToString
     Thread::Sleep(10);
    }
   }
  }
};

int main()
{
 Point^ p = gcnew Point;
 
 /*1*/ ThreadY^ o1 = gcnew ThreadY(true, p);
 /*2*/ Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadY::StartUp));
 
 /*3*/ ThreadY^ o2 = gcnew ThreadY(false, p);
 /*4*/ Thread^ t2 = gcnew Thread(gcnew ThreadStart(o2, &ThreadY::StartUp));

 t1->Start();
 t2->Start();

 Thread::Sleep(100);
 /*5*/ Console::WriteLine("x: {0}", p->X);
 /*6*/ Console::WriteLine("y: {0}", p->Y);

 /*7*/ t1->Join();
 t2->Join();
}

  调用Sleep休眠100毫秒的目的是为了在可以访问x与y坐标之前,让两个线程开始执行,这就是说,我们想要主线程与其他两个线程竞争坐标值的独占访问。

  对Thread::Join的调用将会挂起调用线程,直到Join调用的线程结束。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章