科技行者

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

知识库

知识库 安全导航

至顶网软件频道基础软件实例解析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调用的线程结束。
    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐椋庢閿熺姴鐭楅幖娣妼缁愭鏌¢崶鈺佷汗闁哄閰i弻鏇$疀鐎n亞浠炬繝娈垮灠閵堟悂寮婚弴锛勭杸閻庯綆浜栭崑鎾诲冀椤撱劎绋忛梺璺ㄥ櫐閹凤拷

    濠电姷鏁告慨鐑姐€傛禒瀣劦妞ゆ巻鍋撻柛鐔锋健閸┾偓妞ゆ巻鍋撶紓宥咃躬楠炲啫螣鐠囪尙绐為梺褰掑亰閸撴盯鎮惧ú顏呪拺闂傚牊鍗曢崼銉ョ柧婵犲﹤瀚崣蹇旂節婵犲倻澧涢柛瀣ㄥ妽閵囧嫰寮介妸褋鈧帡鏌熼挊澶婃殻闁哄瞼鍠栭幃婊堝煛閸屾稓褰嬮柣搴ゎ潐濞叉ê鐣濈粙璺ㄦ殾闁割偅娲栭悡娑㈡煕鐏炲墽鐭嬫繛鍫熸倐濮婄粯鎷呯粵瀣異闂佹悶鍔嬮崡鍐茬暦閵忋倕鍐€妞ゆ劑鍎卞皬闂備焦瀵х粙鎴犫偓姘煎弮瀹曚即宕卞Ο闀愮盎闂侀潧鐗嗛幊搴㈡叏椤掆偓閳规垿鍩ラ崱妞剧凹濠电姰鍨洪敋閾荤偞淇婇妶鍛櫤闁稿鍊圭换娑㈠幢濡纰嶉柣搴㈣壘椤︾敻寮诲鍫闂佸憡鎸鹃崰搴敋閿濆鏁嗗〒姘功閻绻涢幘鏉戠劰闁稿鎹囬弻锝呪槈濞嗘劕纾抽梺鍝勬湰缁嬫垿鍩為幋锕€宸濇い鏇炴噺閳诲﹦绱撻崒娆戝妽妞ゃ劌鎳橀幆宀勫磼閻愰潧绁﹂柟鍏肩暘閸斿矂鎮為崹顐犱簻闁圭儤鍨甸鈺呮倵濮橆剦妲归柕鍥у瀵粙濡歌閸c儳绱撴担绛嬪殭婵☆偅绻堝濠氭偄绾拌鲸鏅i悷婊冪Ч閹﹢鎳犻鍌滐紲闁哄鐗勯崝搴g不閻愮儤鐓涢悘鐐跺Г閸犳﹢鏌℃担鐟板鐎规洜鍠栭、姗€鎮╅搹顐ら拻闂傚倷娴囧畷鍨叏閹惰姤鈷旂€广儱顦崹鍌炴煢濡尨绱氶柨婵嗩槸缁€瀣亜閺嶃劎鈽夋繛鍫熺矒濮婅櫣娑甸崨顔俱€愬銈庡亝濞茬喖宕洪埀顒併亜閹哄棗浜鹃梺鎸庢穿婵″洤危閹版澘绫嶉柛顐g箘椤撴椽姊虹紒妯哄鐎殿噮鍓欒灃闁告侗鍠氶崢鎼佹⒑閸撴彃浜介柛瀣閹﹢鏁冮崒娑氬幈闁诲函缍嗛崑鍡樻櫠椤掑倻纾奸柛灞剧☉缁椦囨煙閻熸澘顏柟鐓庢贡閹叉挳宕熼棃娑欐珡闂傚倸鍊风粈渚€骞栭銈傚亾濮樺崬鍘寸€规洖缍婇弻鍡楊吋閸涱垽绱遍柣搴$畭閸庨亶藝娴兼潙纾跨€广儱顦伴悡鏇㈡煛閸ャ儱濡煎褜鍨伴湁闁绘ǹ绉鍫熺畳闂備焦瀵х换鍌毼涘Δ鍛厺闁哄洢鍨洪悡鍐喐濠婂牆绀堟慨妯挎硾閽冪喖鏌曟繛褍瀚烽崑銊╂⒑缂佹ê濮囨い鏇ㄥ弮閸┿垽寮撮姀鈥斥偓鐢告煥濠靛棗鈧懓鈻嶉崶銊d簻闊洦绋愰幉楣冩煛鐏炵偓绀嬬€规洟浜堕、姗€鎮㈡總澶夌处

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