当前位置:天才代写 > tutorial > C语言/C++ 教程 > 实例理会C++/CLI线程之线程状态耐久性

实例理会C++/CLI线程之线程状态耐久性

2017-11-07 08:00 星期二 所属: C语言/C++ 教程 浏览:506

副标题#e#

其他形式的同步

我们可利用类Monitor与类Thread中的某些函数,直接节制线程的同步,请看例1。

例1:

using namespace System;
using namespace System::Threading;
int main()
{
  /*1*/ MessageBuffer^ m = gcnew MessageBuffer;
  /*2a*/ ProcessMessages^ pm = gcnew ProcessMessages(m);
  /*2b*/ Thread^ pmt = gcnew Thread(gcnew ThreadStart(pm,&ProcessMessages::ProcessMessagesEntryPoint));
  /*2c*/ pmt->Start();
  /*3a*/ CreateMessages^ cm = gcnew CreateMessages(m);
  /*3b*/ Thread^ cmt = gcnew Thread(gcnew ThreadStart(cm, &CreateMessages::CreateMessagesEntryPoint));
  /*3c*/ cmt->Start();
  /*4*/ cmt->Join();
  /*5*/ pmt->Interrupt();
  /*6*/ pmt->Join();
  Console::WriteLine("Primary thread terminating");
}
public ref class MessageBuffer
{
  String^ messageText;
  public:
   void SetMessage(String^ s)
   {
    /*7*/ Monitor::Enter(this);
    messageText = s;
    /*8*/ Monitor::Pulse(this);
    Console::WriteLine("Set new message {0}", messageText);
    Monitor::Exit(this);
   }
   void ProcessMessages()
   {
    /*9*/ Monitor::Enter(this);
    while (true)
    {
     try
     {
      /*10*/ Monitor::Wait(this);
     }
     catch (ThreadInterruptedException^ e)
     {
      Console::WriteLine("ProcessMessage interrupted");
      return;
     }
     Console::WriteLine("Processed new message {0}", messageText);
    }
    Monitor::Exit(this);
   }
};
public ref class CreateMessages
{
  MessageBuffer^ msg;
  public:
   CreateMessages(MessageBuffer^ m)
   {
    msg = m;
   }
   void CreateMessagesEntryPoint()
   {
    for (int i = 1; i <= 5; ++i)
    {
     msg->SetMessage(String::Concat("M-", i.ToString()));
     Thread::Sleep(2000);
    }
    Console::WriteLine("CreateMessages thread terminating");
   }
};
public ref class ProcessMessages
{
  MessageBuffer^ msg;
  public:
   ProcessMessages(MessageBuffer^ m)
   {
    msg = m;
   }
   void ProcessMessagesEntryPoint()
   {
    msg->ProcessMessages();
    Console::WriteLine("ProcessMessages thread terminating");
   }
};


#p#副标题#e#

在标志1中,建设一个MessageBuffer范例的共享缓冲区;接着在标志2a、2b、2c中,建设了一个线程用于处理惩罚安排于缓冲区中的每条信息;标志3a、3b和3c,也建设了一个线程,并在共享缓冲区中安排了持续的5条信息以便处理惩罚。这两个线程已被同步,因此处理惩罚者线程必需比及有"对象"放入到缓冲区中,才可以举办处理惩罚,且在前一条信息被处理惩罚完之前,不能放入第二条信息。在标志4中,将一直期待,直到建设者线程完成它的事情。

当标志5执行时,处理惩罚者线程必需处理惩罚所有建设者线程放入的信息,因为利用了Thread::Interrupt让其遏制事情,并继承期待标志6中挪用的Thread::Join,这个函数答允挪用线程阻塞它本身,直到其他线程竣事。(一个线程可指定一个期待的最大时间,而不消无限期待下去。)

线程CreateMessages很是清晰明白,它向共享缓冲区中写入了5条信息,并在每条信息之间期待2秒。为把一个线程挂起一个给定的时间(以毫秒计),我们挪用了Thread::Sleep,在此,一个睡眠的线程可再继承执行,原因在于运行时情况,而不是另一个线程。

线程ProcessMessages甚至越发简朴,因为它操作了类MessageBuffer来做它的所有事情。类MessageBuffer中的函数是被同步的,因此在同一时间,只有一个函数能会见共享缓冲区。

主措施首先启动处理惩罚者线程,这个线程会执行ProcessMessages,其将得到父工具的同步锁;然而,它当即挪用了标志10中的Wait函数,这个函数将让它一直期待,直到再次被告之运行,期间,它也交出了同步锁,这样,答允建设者线程获得同步锁并执行SetMessage。一旦函数把新的信息放入到共享缓冲区中,就会挪用标志8中的Pulse,其答允期待同步锁的任意线程被叫醒,并继承执行下去。可是,在SetMessage执行完成之前,这些都不行能产生,因为它在函数返回前都不行能交出同步锁。假如环境一旦产生,处理惩罚者线程将从头获得同步锁,并从标志10之后开始继承执行。此处要说明的是,一个线程即可无限期待,也可比及一个指定的时间达到。插1是措施的输出。

插1:

#p#分页标题#e#

Set new message M-1
Processed new message M-1
Set new message M-2
Processed new message M-2
Set new message M-3
Processed new message M-3
Set new message M-4
Processed new message M-4
Set new message M-5
Processed new message M-5
CreateMessages thread terminating
ProcessMessage interrupted
ProcessMessages thread terminating
Primary thread terminating

请仔细寄望,处理惩罚者线程启动于建设者线程之前。假如以相反的顺序启动,将会在没有处理惩罚者线程期待的环境下,添加第一条信息,此时,没有可供叫醒处理惩罚者线程,当处理惩罚者线程运行到它的第一个函数挪用Wait时,将会错过第一条信息,且只会在第二条信息存储时被叫醒。

打点线程

默认环境下,假如一个线程是前台线程,它将会一直执行下去,直到进入点函数竣事,而不管它父类的生命期是多久;而在另一方面,靠山线程则会在父类线程竣事时自动竣事。可通过配置Thread的IsBackground属性,把一个线程设置为靠山线程,用同样的要领,也可把一个靠山线程设置为前台线程。

一旦线程被启动,它即为活泼线程,可通过查抄Thread的IsAlive属性来判定一个线程是否为活泼线程;通过挪用Wait函数,并通报给它一个零毫秒,可使一个线程放弃剩余的CPU时间片;别的,线程还可通过CurrentThread::Thread::CurrentThread属性获得其本身的Thread工具。

每个线程都有与之相关的优先级,运行时情况(即操纵系统)通过它来调治线程的执行,可通过Thread::Priority属性来配置或检测线程的优先级,它的范畴从ThreadPriority::Lowest 到ThreadPriority::Highest,默认环境下,线程的优先级为ThreadPriority::Normal。别的,因为实现情况的差异,线程调治会有所差异,所以在节制线程方面,不该该过度依赖线程的优先级。

#p#副标题#e#

易变字段(域)

volatile这个限定范例汇报编译器,大概会有多个线程节制或会见它所指定的工具,尤其是,一个或多个线程大概将异步读写此变量。根基上,这个限定词是强制编译器在举办优化时不要那么"激进"。

请看例2中的代码段,在缺少volatile时,标志1中的代码完全可以忽略,因为在标志2中当即就改写了i的值;然而,指定了volatile后,编译器则必需执行这两行代码。

例2:

volatile int i = 0;
/*1*/ i = 10;
/*2*/ i = 20;
/*3*/ if (i < 5 || i > 10) {
// ...
}
int copy = i;
/*4*/ if (copy < 5 || copy > 10) {
// ...
}

在标志3中,编译器必需生成取回值i的代码两次,可是,在两次取值进程中,数值都有大概改变。为确保我们测试的是同一个值,在此不得不以雷同标志4的代码来取代。通过把值i的一个快照存储在一个非易变的变量中,我们就可以安详地多次利用这个值了–因为它的值不行能在"靠山"改变。在此,利用volatile,可制止对特定范例变量的显式异步会见。

线程局部存储

当编写多线程应用措施时,只在特定的线程中利用特定的变量,这是一个很是好的习惯,请看例3的措施:

例3:

using namespace System;
using namespace System::Threading;
public ref class ThreadX
{
  /*1*/ int m1;
  /*2*/ static int m2 = 20;
  /*3*/ [ThreadStatic] static int m3 = 30;
  public:
   ThreadX()
   {
    m1 = 10;
   }
 
   void TMain()
   {
    String^ threadName = Thread::CurrentThread->Name;
    /*4*/ Monitor::Enter(ThreadX::typeid);
    for (int i = 1; i <= 5; ++i)
    {
     ++m1;
     ++m2;
     ++m3;
    }
    Console::WriteLine("Thread {0}: m1 = {1}, m2 = {2}, m3 = {3}",
threadName, m1, m2, m3);
    Monitor::Exit(ThreadX::typeid);
  }
};
int main()
{
  /*5*/ Thread::CurrentThread->Name = "t0";
  ThreadX^ o1 = gcnew ThreadX;
  Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadX::TMain));
  t1->Name = "t1";
  ThreadX^ o2 = gcnew ThreadX;
  Thread^ t2 = gcnew Thread(gcnew ThreadStart(o2, &ThreadX::TMain));
  t2->Name = "t2";
  t1->Start();
  /*6*/ (gcnew ThreadX)->TMain();
  t2->Start();
  t1->Join();
  t2->Join();
}

#p#副标题#e#

#p#分页标题#e#

m1是一个实例字段,所以每个ThreadX的实例都有一份各自的拷贝,且在父类工具的生命期中城市存在;而另一方面,m2是一个类字段,所以对类来说,不管有几个类的实例,它只有单独的一个,从理论上来说,它将会一直存在,直到措施竣事。但这两个字段都不是特定于某个线程的,假如以适当的结构,这两种范例的字段都能被多个线程会见。

简朴来说,线程局部存储就是特定线程拥有的某段内存,这段内存在新线程建设时被分派,而在线程竣事时被释放,它团结结局部变量的私有性和静态变量的耐久性。通过指定ThreadStatic属性,可把一个字段标志为线程局部范例,如例中的标志3所示,在成为静态字段之后,m3甚至还能有一个初始化函数。

函数TMain为新线程的进口点,这个函数只是简朴地递增这三个变量:m1、m2和m3,每回5次,并打印出它们当前的值。标志4中的同步锁担保了在这些字段递增或打印时,另一个线程不会同时会见它们。

在标志5中,主线程把它的名字配置为t0,接着建设并启动了两个线程,别的,它也把TMain看成了一个普通函数直接挪用,而不是作为建设的新线程的一部门来挪用。措施的输出请见插2。

插2:

Thread t0: m1 = 15, m2 = 25, m3 = 35
Thread t1: m1 = 15, m2 = 30, m3 = 5
Thread t2: m1 = 15, m2 = 35, m3 = 5

每个线程都有其本身的m1实例,它被初始化为10,所以在递增5次之后,每个线程中的值都为15。而m2则有所差异,所有的三个线程都共享同一变量,所以这一变量被递增了15次。

线程t1与t2在颠末线程建设进程之后,每个都有其本身的m3,然而,这些线程局部变量会被赋予默认的零值,而不是在源代码中初始化的30,留意了,在颠末5次递增之后,各个值均为5,而线程t0则有所差异,正如我们所看到的,这个线程不是由建设其他两个线程同样的机制建设的,所以,它的m3会接管显式初始化的值30。同时也请留意标志6,TMain作为一个普通函数被挪用,而不是作为建设的新线程的一部门。

#p#副标题#e#

原子性与互锁操纵

假如存在这样一种环境:一个应用措施有多个线程并行运行,每个线程对某些共享的整形变量,都有写操纵–只是简朴地利用++把变量递增1。这看起来好像没什么问题,究竟,还算像是一个原子性操纵,但在大都系统中–至少从呆板指令的角度来看,C++/CLI执行情况对所有整形范例,并不能普各处担保无误。

作为示例,例4中的措施有三个线程,每个线程都同时递增一个共享的64位整形变量一千万次,最后显示出这个变量的最终值,从理论上说,应该共递增了三千万次。这个措施今朝可以两种方法运行:默认方法利用++操纵符以非同步方法运行;而另一种方法,通过带有呼吁行参数Y或y,这回利用了一个同步的库递增函数。

例4:

using namespace System;
using namespace System::Threading;
static bool interlocked = false;
const int maxCount = 10000000;
/*1*/ static long long value = 0;
void TMain()
{
  if (interlocked)
  {
   for (int i = 1; i <= maxCount; ++i)
   {
    /*2*/ Interlocked::Increment(value);
   }
  }
  else
  {
   for (int i = 1; i <= maxCount; ++i)
   {
    /*3*/ ++value;
   }
  }
}
int main(array<String^>^ argv)
{
  if (argv->Length == 1)
  {
   if (argv[0]->Equals("Y") || argv[0]->Equals("y"))
   {
    interlocked = true;
   }
  }
  /*4*/ Thread^ t1 = gcnew Thread(gcnew ThreadStart(&TMain));
  Thread^ t2 = gcnew Thread(gcnew ThreadStart(&TMain));
  Thread^ t3 = gcnew Thread(gcnew ThreadStart(&TMain));
  t1->Start();
  t2->Start();
  t3->Start();
  t1->Join();
  t2->Join();
  t3->Join();
  Console::WriteLine("After {0} operations, value = {1}", 3 * maxCount, value);
}

当利用尺度++操纵符时,措施5次持续执行之后,输出如插3所示,可看出,功效与正确谜底相距甚远,简朴估算,或许有17%至50%的递增操纵未正确完成;当措施运行于同步方法时–纵然用Interlocked::Increment,所有的三千万次递增操纵都正常完成,功效计较正确。

插3:

利用++操纵符的输出

#p#分页标题#e#

After 30000000 operations, value = 14323443
After 30000000 operations, value = 24521969
After 30000000 operations, value = 20000000
After 30000000 operations, value = 24245882
After 30000000 operations, value = 25404963

利用Interlocked递增函数的输出

After 30000000 operations, value = 30000000

别的,增补一点,Interlocked类尚有另一个decrement函数。

 

    关键字:

天才代写-代写联系方式