C#中的线程(一)入门

2023-11-15

文章系参考转载,英文原文网址请参考:http://www.albahari.com/threading/

作者 Joseph Albahari,  翻译 Swanky Wu

  中文翻译作者把原文放在了"google 协作"上面,GFW屏蔽,不能访问和查看,因此我根据译文和英文原版整理转载到园子里面。

  本系列文章可以算是一本很出色的C#线程手册,思路清晰,要点都有介绍,看了后对C#的线程及同步等有了更深入的理解。

  • 入门
  • 线程同步基础
    • 同步要领
    • 锁和线程安全
    • Interrupt 和 Abort
    • 线程状态
    • 等待句柄
    • 同步环境
  • 使用多线程
    • 单元模式和Windows Forms
    • BackgroundWorker类
    • ReaderWriterLock类
    • 线程池
    • 异步委托
    • 计时器
    • 局部储存
  • 高级话题
    • 非阻止同步
    • Wait和Pulse
    • Suspend和Resume
    • 终止线程

一、入门

1.     概述与概念

   C#支持通过多线程并行地执行代码,一个线程有它独立的执行路径,能够与其它的线程同时地运行。一个C#程序开始于一个单线程,这个单线程是被CLR和操作系统(也称为“主线程”)自动创建的,并具有多线程创建额外的线程。这里的一个简单的例子及其输出:

     除非被指定,否则所有的例子都假定以下命名空间被引用了:  
   
using System; 
   using System.Threading;

1
2
3
4
5
6
7
8
9
10
11
class  ThreadTest {
   static  void  Main() {
     Thread t = new  Thread (WriteY);
     t.Start();                          // Run WriteY on the new thread
     while  ( true ) Console.Write ( "x" );   // Write 'x' forever
   }
  
   static  void  WriteY() {
     while  ( true ) Console.Write ( "y" );   // Write 'y' forever
   }
}

image

   主线程创建了一个新线程“t”,它运行了一个重复打印字母"y"的方法,同时主线程重复但因字母“x”。CLR分配每个线程到它自己的内存堆栈上,来保证局部变量的分离运行。在接下来的方法中我们定义了一个局部变量,然后在主线程和新创建的线程上同时地调用这个方法。

1
2
3
4
5
6
7
8
9
static  void  Main() {
   new  Thread (Go).Start();      // Call Go() on a new thread
   Go();                         // Call Go() on the main thread
}
  
static  void  Go() {
   // Declare and use a local variable - 'cycles'
   for  ( int  cycles = 0; cycles < 5; cycles++) Console.Write ( '?' );
}

image

   变量cycles的副本分别在各自的内存堆栈中创建,输出也一样,可预见,会有10个问号输出。当线程们引用了一些公用的目标实例的时候,他们会共享数据。下面是实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class  ThreadTest {
  bool  done;
  
  static  void  Main() {
    ThreadTest tt = new  ThreadTest();   // Create a common instance
    new  Thread (tt.Go).Start();
    tt.Go();
  }
  
  // Note that Go is now an instance method
  void  Go() {
    if  (!done) { done = true ; Console.WriteLine ( "Done" ); }
  }
}
因为在相同的<b>ThreadTest</b>实例中,两个线程都调用了<b>Go()</b>,它们共享了<b>done</b>字段,这个结果输出的是一个 "Done" ,而不是两个。
1
<a href= "http://images.cnblogs.com/cnblogs_com/miniwiki/WindowsLiveWriter/C_12936/image_6.png" ><img height= "45"  width= "640"  src= "http://images.cnblogs.com/cnblogs_com/miniwiki/WindowsLiveWriter/C_12936/image_thumb_2.png"  align= "left"  alt= "image"  border= "0"  title= "image"  style= "display: inline; margin-left: 0px; margin-right: 0px; border-width: 0px;" ></a>

 

  静态字段提供了另一种在线程间共享数据的方式,下面是一个以done为静态字段的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class  ThreadTest {
  static  bool  done;    // Static fields are shared between all threads
  
  static  void  Main() {
    new  Thread (Go).Start();
    Go();
  }
  
  static  void  Go() {
    if  (!done) { done = true ; Console.WriteLine ( "Done" ); }
  }
}

  上述两个例子足以说明, 另一个关键概念, 那就是线程安全(或反之,它的不足之处! ) 输出实际上是不确定的:它可能(虽然不大可能) , "Done" ,可以被打印两次。然而,如果我们在Go方法里调换指令的顺序, "Done"被打印两次的机会会大幅地上升:

1
2
3
static  void  Go() {
   if  (!done) { Console.WriteLine ( "Done" ); done = true ; }
}

image

问题就是一个线程在判断if块的时候,正好另一个线程正在执行WriteLine语句——在它将done设置为true之前。

补救措施是当读写公共字段的时候,提供一个排他锁;C#提供了lock语句来达到这个目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class  ThreadSafe {
   static  bool  done;
   static  object  locker = new  object ();
  
   static  void  Main() {
     new  Thread (Go).Start();
     Go();
   }
  
   static  void  Go() {
     lock  (locker) {
       if  (!done) { Console.WriteLine ( "Done" ); done = true ; }
     }
   }
}

   当两个线程争夺一个锁的时候(在这个例子里是locker),一个线程等待,或者说被阻止到那个锁变的可用。在这种情况下,就确保了在同一时刻只有一个线程能进入临界区,所以"Done"只被打印了1次。代码以如此方式在不确定的多线程环境中被叫做线程安全

   临时暂停,或阻止是多线程的协同工作,同步活动的本质特征。等待一个排它锁被释放是一个线程被阻止的原因,另一个原因是线程想要暂停或Sleep一段时间:

1
Thread.Sleep (TimeSpan.FromSeconds (30));         // Block for 30 seconds

一个线程也可以使用它的Join方法来等待另一个线程结束:

1
2
3
Thread t = new  Thread (Go);           // Assume Go is some static method
t.Start();
t.Join();                             // Wait (block) until thread t ends

一个线程,一旦被阻止,它就不再消耗CPU的资源了。

  线程是如何工作的

   线程被一个线程协调程序管理着——一个CLR委托给操作系统的函数。线程协调程序确保将所有活动的线程被分配适当的执行时间;并且那些等待或阻止的线程——比如说在排它锁中、或在用户输入——都是不消耗CPU时间的。

   在单核处理器的电脑中,线程协调程序完成一个时间片之后迅速地在活动的线程之间进行切换执行。这就导致“波涛汹涌”的行为,例如在第一个例子,每次重复的X 或 Y 块相当于分给线程的时间片。在Windows XP中时间片通常在10毫秒内选择要比CPU开销在处理线程切换的时候的消耗大的多。(即通常在几微秒区间)

   在多核的电脑中,多线程被实现成混合时间片和真实的并发——不同的线程在不同的CPU上运行。这几乎可以肯定仍然会出现一些时间切片, 由于操作系统的需要服务自己的线程,以及一些其他的应用程序。

   线程由于外部因素(比如时间片)被中断被称为被抢占,在大多数情况下,一个线程方面在被抢占的那一时那一刻就失去了对它的控制权。

   线程 vs. 进程

    属于一个单一的应用程序的所有的线程逻辑上被包含在一个进程中,进程指一个应用程序所运行的操作系统单元。

    线程于进程有某些相似的地方:比如说进程通常以时间片方式与其它在电脑中运行的进程的方式与一个C#程序线程运行的方式大致相同。二者的关键区别在于进程彼此是完全隔绝的。线程与运行在相同程序其它线程共享(堆heap)内存,这就是线程为何如此有用:一个线程可以在后台读取数据,而另一个线程可以在前台展现已读取的数据。

  何时使用多线程

    多线程程序一般被用来在后台执行耗时的任务。主线程保持运行,并且工作线程做它的后台工作。对于Windows Forms程序来说,如果主线程试图执行冗长的操作,键盘和鼠标的操作会变的迟钝,程序也会失去响应。由于这个原因,应该在工作线程中运行一个耗时任务时添加一个工作线程,即使在主线程上有一个有好的提示“处理中...”,以防止工作无法继续。这就避免了程序出现由操作系统提示的“没有相应”,来诱使用户强制结束程序的进程而导致错误。模式对话框还允许实现“取消”功能,允许继续接收事件,而实际的任务已被工作线程完成。BackgroundWorker恰好可以辅助完成这一功能。

   在没有用户界面的程序里,比如说Windows Service, 多线程在当一个任务有潜在的耗时,因为它在等待另台电脑的响应(比如一个应用服务器,数据库服务器,或者一个客户端)的实现特别有意义。用工作线程完成任务意味着主线程可以立即做其它的事情。

   另一个多线程的用途是在方法中完成一个复杂的计算工作。这个方法会在多核的电脑上运行的更快,如果工作量被多个线程分开的话(使用Environment.ProcessorCount属性来侦测处理芯片的数量)。

   一个C#程序称为多线程的可以通过2种方式:明确地创建和运行多线程,或者使用.NET framework的暗中使用了多线程的特性——比如BackgroundWorker类, 线程池threading timer,远程服务器,或Web Services或ASP.NET程序。在后面的情况,人们别无选择,必须使用多线程;一个单线程的ASP.NET web server不是太酷,即使有这样的事情;幸运的是,应用服务器中多线程是相当普遍的;唯一值得关心的是提供适当锁机制的静态变量问题。

  何时不要使用多线程

    多线程也同样会带来缺点,最大的问题是它使程序变的过于复杂,拥有多线程本身并不复杂,复杂是的线程的交互作用,这带来了无论是否交互是否是有意的,都会带来较长的开发周期,以及带来间歇性和非重复性的bugs。因此,要么多线程的交互设计简单一些,要么就根本不使用多线程。除非你有强烈的重写和调试欲望。

当用户频繁地分配和切换线程时,多线程会带来增加资源和CPU的开销。在某些情况下,太多的I/O操作是非常棘手的,当只有一个或两个工作线程要比有众多的线程在相同时间执行任务块的多。稍后我们将实现生产者/耗费者 队列,它提供了上述功能。

 

2.    创建和开始使用多线程

   线程用Thread类来创建, 通过ThreadStart委托来指明方法从哪里开始运行,下面是ThreadStart委托如何定义的:

1
public  delegate  void  ThreadStart();

   调用Start方法后,线程开始运行,线程一直到它所调用的方法返回后结束。下面是一个例子,使用了C#的语法创建TheadStart委托:

1
2
3
4
5
6
7
class  ThreadTest {
   static  void  Main() {
     Thread t = new  Thread ( new  ThreadStart (Go));
     t.Start();   // Run Go() on the new thread.
     Go();        // Simultaneously run Go() in the main thread.
   }
   static  void  Go() { Console.WriteLine ( "hello!" ); }

在这个例子中,线程t执行Go()方法,大约与此同时主线程也调用了Go(),结果是两个几乎同时hello被打印出来:

image

一个线程可以通过C#堆委托简短的语法更便利地创建出来:

1
2
3
4
5
6
7
static  void  Main() {
   Thread t = new  Thread (Go);    // No need to explicitly use ThreadStart
   t.Start();
   ...
}
static  void  Go() { ... }
在这种情况,ThreadStart被编译器自动推断出来,另一个快捷的方式是使用匿名方法来启动线程:
1
2
3
4
static  void  Main() {
   Thread t = new  Thread ( delegate () { Console.WriteLine ( "Hello!" ); });
   t.Start();
}

  线程有一个IsAlive属性,在调用Start()之后直到线程结束之前一直为true。一个线程一旦结束便不能重新开始了。

  将数据传入ThreadStart中

  话又说回来,在上面的例子里,我们想更好地区分开每个线程的输出结果,让其中一个线程输出大写字母。我们传入一个状态字到Go中来完成整个任务,但我们不能使用ThreadStart委托,因为它不接受参数,所幸的是,.NET framework定义了另一个版本的委托叫做ParameterizedThreadStart, 它可以接收一个单独的object类型参数:

1
2
public  delegate  void  ParameterizedThreadStart ( object  obj);
之前的例子看起来是这样的:
1
  
1
2
3
4
5
6
7
8
9
10
class  ThreadTest {
   static  void  Main() {
     Thread t = new  Thread (Go);
     t.Start ( true );             // == Go (true)
     Go ( false );
   }
   static  void  Go ( object  upperCase) {
     bool  upper = ( bool ) upperCase;
     Console.WriteLine (upper ? "HELLO!"  : "hello!" );
   }

image

  在整个例子中,编译器自动推断出ParameterizedThreadStart委托,因为Go方法接收一个单独的object参数,就像这样写:

1
2
Thread t = new  Thread ( new  ParameterizedThreadStart (Go));
t.Start ( true );

ParameterizedThreadStart的特性是在使用之前我们必需对我们想要的类型(这里是bool)进行装箱操作,并且它只能接收一个参数。

  一个替代方案是使用一个匿名方法调用一个普通的方法如下:

1
2
3
4
5
static  void  Main() {
   Thread t = new  Thread ( delegate () { WriteText ( "Hello" ); });
   t.Start();
}
static  void  WriteText ( string  text) { Console.WriteLine (text); }

  优点是目标方法(这里是WriteText),可以接收任意数量的参数,并且没有装箱操作。不过这需要将一个外部变量放入到匿名方法中,向下面的一样:

1
2
3
4
5
6
7
static  void  Main() {
   string  text = "Before" ;
   Thread t = new  Thread ( delegate () { WriteText (text); });
   text = "After" ;
   t.Start();
}
static  void  WriteText ( string  text) { Console.WriteLine (text); }

image

  匿名方法打开了一种怪异的现象,当外部变量被后来的部分修改了值的时候,可能会透过外部变量进行无意的互动。有意的互动(通常通过字段)被认为是足够了!一旦线程开始运行了,外部变量最好被处理成只读的——除非有人愿意使用适当的锁。

  另一种较常见的方式是将对象实例的方法而不是静态方法传入到线程中,对象实例的属性可以告诉线程要做什么,如下列重写了原来的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class  ThreadTest {
   bool  upper;
  
   static  void  Main() {
     ThreadTest instance1 = new  ThreadTest();
     instance1.upper = true ;
     Thread t = new  Thread (instance1.Go);
     t.Start();
     ThreadTest instance2 = new  ThreadTest();
     instance2.Go();        // 主线程——运行 upper=false
   }
  
   void  Go() { Console.WriteLine (upper ? "HELLO!"  : "hello!" ); }

  命名线程

  线程可以通过它的Name属性进行命名,这非产有利于调试:可以用Console.WriteLine打印出线程的名字,Microsoft Visual Studio可以将线程的名字显示在调试工具栏的位置上。线程的名字可以在被任何时间设置——但只能设置一次,重命名会引发异常。

  程序的主线程也可以被命名,下面例子里主线程通过CurrentThread命名:

1
2
3
4
5
6
7
8
9
10
11
12
class  ThreadNaming {
   static  void  Main() {
     Thread.CurrentThread.Name = "main" ;
     Thread worker = new  Thread (Go);
     worker.Name = "worker" ;
     worker.Start();
     Go();
   }
   static  void  Go() {
     Console.WriteLine ( "Hello from "  + Thread.CurrentThread.Name);
   }
}

image

  

  前台和后台线程

  线程默认为前台线程,这意味着任何前台线程在运行都会保持程序存活。C#也支持后台线程,当所有前台线程结束后,它们不维持程序的存活。

  改变线程从前台到后台不会以任何方式改变它在CPU协调程序中的优先级和状态。

  线程的IsBackground属性控制它的前后台状态,如下实例:

1
2
3
4
5
6
7
class  PriorityTest {
   static  void  Main ( string [] args) {
     Thread worker = new  Thread ( delegate () { Console.ReadLine(); });
     if  (args.Length > 0) worker.IsBackground = true ;
     worker.Start();
   }
}

   如果程序被调用的时候没有任何参数,工作线程为前台线程,并且将等待ReadLine语句来等待用户的触发回车,这期间,主线程退出,但是程序保持运行,因为一个前台线程仍然活着。

   另一方面如果有参数传入Main(),工作线程被赋值为后台线程,当主线程结束程序立刻退出,终止了ReadLine。

   后台线程终止的这种方式,使任何最后操作都被规避了,这种方式是不太合适的。好的方式是明确等待任何后台工作线程完成后再结束程序,可能用一个timeout(大多用Thread.Join)。如果因为某种原因某个工作线程无法完成,可以用试图终止它的方式,如果失败了,再抛弃线程,允许它与 与进程一起消亡。(记录是一个难题,但这个场景下是有意义的)

   拥有一个后台工作线程是有益的,最直接的理由是它当提到结束程序它总是可能有最后的发言权。交织以不会消亡的前台线程,保证程序的正常退出。抛弃一个前台工作线程是尤为险恶的,尤其对Windows Forms程序,因为程序直到主线程结束时才退出(至少对用户来说),但是它的进程仍然运行着。在Windows任务管理器它将从应用程序栏消失不见,但却可以在进程栏找到它。除非用户找到并结束它,它将继续消耗资源,并可能阻止一个新的实例的运行从开始或影响它的特性。

   对于程序失败退出的普遍原因就是存在“被忘记”的前台线程。

 

  线程优先级

  线程的Priority 属性确定了线程相对于其它同一进程的活动的线程拥有多少执行时间,以下是级别:

1
enum  ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

  只有多个线程同时为活动时,优先级才有作用。

  设置一个线程的优先级为高一些,并不意味着它能执行实时的工作,因为它受限于程序的进程的级别。要执行实时的工作,必须提升在System.Diagnostics 命名空间下Process的级别,像下面这样:(我没有告诉你如何做到这一点:))

1
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

   ProcessPriorityClass.High 其实是一个短暂缺口的过程中的最高优先级别:Realtime。设置进程级别到Realtime通知操作系统:你不想让你的进程被抢占了。如果你的程序进入一个偶然的死循环,可以预期,操作系统被锁住了,除了关机没有什么可以拯救你了!基于此,High大体上被认为最高的有用进程级别。

   如果一个实时的程序有一个用户界面,提升进程的级别是不太好的,因为当用户界面UI过于复杂的时候,界面的更新耗费过多的CPU时间,拖慢了整台电脑。(虽然在写这篇文章的时候,在互联网电话程序Skype侥幸地这么做, 也许是因为它的界面相当简单吧。) 降低主线程的级别、提升进程的级别、确保实时线程不进行界面刷新,但这样并不能避免电脑越来越慢,因为操作系统仍会拨出过多的CPU给整个进程。最理想的方案是使实时工作和用户界面在不同的进程(拥有不同的优先级)运行,通过Remoting或共享内存方式进行通信,共享内存需要Win32 API中的 P/Invoking。(可以搜索看看CreateFileMapping  MapViewOfFile)

  

  异常处理

  任何线程创建范围内try/catch/finally块,当线程开始执行便不再与其有任何关系。考虑下面的程序:

1
2
3
4
5
6
7
8
9
10
11
public  static  void  Main() {
  try  {
    new  Thread (Go).Start();
  }
  catch  (Exception ex) {
    // 不会在这得到异常
    Console.WriteLine ( "Exception!" );
  }
 
  static  void  Go() { throw  null ; }
}
1
这里 try / catch 语句一点用也没有,新创建的线程将引发NullReferenceException异常。当你考虑到每个线程有独立的执行路径的时候,便知道这行为是有道理的,
1
补救方法是在线程处理的方法内加入他们自己的异常处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  static  void  Main() {
    new  Thread (Go).Start();
}
  
static  void  Go() {
   try  {
     ...
     throw  null ;      // 这个异常在下面会被捕捉到
     ...
   }
   catch  (Exception ex) {
     记录异常日志,并且或通知另一个线程
     我们发生错误
     ...
   }

   从.NET 2.0开始,任何线程内的未处理的异常都将导致整个程序关闭,这意味着忽略异常不再是一个选项了。因此为了避免由未处理异常引起的程序崩溃,try/catch块需要出现在每个线程进入的方法内,至少要在产品程序中应该如此。对于经常使用“全局”异常处理的Windows Forms程序员来说,这可能有点麻烦,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using  System;
using  System.Threading;
using  System.Windows.Forms;
  
static  class  Program {
   static  void  Main() {
     Application.ThreadException += HandleError;
     Application.Run ( new  MainForm());
   }
  
   static  void  HandleError ( object  sender, ThreadExceptionEventArgs e) {
     记录异常或者退出程序或者继续运行...
   }
}

 

Application.ThreadException事件在异常被抛出时触发,以一个Windows信息(比如:键盘,鼠标活着 "paint" 等信息)的方式,简言之,一个Windows Forms程序的几乎所有代码。虽然这看起来很完美,它使人产生一种虚假的安全感——所有的异常都被中央异常处理捕捉到了。由工作线程抛出的异常便是一个没有被Application.ThreadException捕捉到的很好的例外。(在Main方法中的代码,包括构造器的形式,在Windows信息开始前先执行)

.NET framework为全局异常处理提供了一个更低级别的事件:AppDomain.UnhandledException,这个事件在任何类型的程序(有或没有用户界面)的任何线程有任何未处理的异常触发。尽管它提供了好的不得已的异常处理解决机制,但是这不意味着这能保证程序不崩溃,也不意味着能取消.NET异常对话框。

在产品程序中,明确地使用异常处理在所有线程进入的方法中是必要的,可以使用包装类和帮助类来分解工作来完成任务,比如使用BackgroundWorker类(在第三部分进行讨论)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中的线程(一)入门 的相关文章

  • 关于 volatile——可见性,有序性,内存屏障

    并发编程的三大特性 原子性 有序性 可见性 从这三个方面去看一下 volatile volatile 保证了可见性 public class Demo1 private boolean flag true public void test
  • C# Task异步编程

    Task任务用法 Task用的是线程池 线程池的线程数量的有上限的 这个可以通过ThreadPool修改 我们经常会用到task run new task 和task factory startnew方法来创建任务 Task Factory
  • 线程同步和线程死锁

    线程同步 前面刚介绍了有关线程的基本认识 那我们先来思考一个小问题 两个线程之间有没有可能同时对一个资源发起访问呢 答案是肯定 那么在某些情况下这样的同时访问会引发一系列冲突 先来看一个简单的例子 创建两个线程 各自将count增加2500
  • Java 四种线程池newCachedThreadPool,newFixedThreadPool,newScheduledThreadPool,newSingleThreadExecutor

    介绍new Thread的弊端及Java四种线程池的使用 对Android同样适用 1 new Thread的弊端 执行一个异步任务你还只是如下new Thread吗 new Thread new Runnable Override pub
  • 深入理解Java内存模型

    作者 谢照东 链接 https www zhihu com question 29037280 answer 43136323 来源 知乎 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 我只是编码界的搬运工 深入理解J
  • C#中async/await的线程ID变化情况

    一 简单的起步 Console WriteLine 主线程开始ID Thread CurrentThread ManagedThreadId a await Task Delay 100 c Console WriteLine 主线程结束I
  • Qt 中开启线程的五种方式

    作者 billy 版权声明 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 简介 在开发过程中 使用线程是经常会遇到的场景 本篇文章就来整理一下 Qt 中使用线程的五种方式 方便后期回顾 前面两种比较简单 一笔带过了 主
  • 线程共享和私有的数据

    引用 windows程序员面试指南 堆上的数据是线程共享的 栈上的数据是线程私有的 1 堆上共享的数据 a 进程 代码段 b 进程 数据段 c 进程打开的文件描述符 d 信号的处理器 e 进程的当前目录和 f 进程用户 ID 与进程组 ID
  • 多线程编程技巧

    java中 多线程类需要继承Thread或实现Runnable接口 在Run函数中执行多线程代码 但是需要用Start 函数开始执行 多线程并行执行 执行的顺序取决于本地操作系统给谁分配系统资源 Runnable共享资源的方法 a 如果每个
  • Java异步注解@Async详解

    一 Async注解 Async的作用就是异步处理任务 在方法上添加 Async 表示此方法是异步方法 在类上添加 Async 表示类中的所有方法都是异步方法 使用此注解的类 必须是Spring管理的类 需要在启动类或配置类中加入 Enabl
  • RTThread:静态线程&动态线程

    一 静态线程创建 rt thread init rt err t rt thread init struct rt thread thread const char name void void parameter entry void p
  • 计算机操作系统知识架构整理

    计算机操作系统 操作系统引论 操作系统的目标与应用 操作系统的目标 操作系统的作用 推动操作系统发展的主要动力 操作系统的发展过程 无操作系统的计算机系统 单道批处理系统 多道批处理系统 分时系统 实时系统 微机操作系统的发展 操作系统的基
  • 操作系统-线程

    说明 文中内容大部分都是大部分都是 操作系统 精髓与设计原理 第八版 的原文 自己做了一些删改 使其更易于理解 本章讲述一些与进程管理相关的高级概念 这些概念在很多现代操作系统中都可以找到 实际上 它包含了两个独立的概念 一个与资源所有权有
  • C#学习笔记 异步操作

    同步操作 默认情况下我们的代码都是同步操作 这种情况下 所有的操作都在同一个线程中 如果遇到需要长时间执行的操作或者是一个IO操作 那么代码可能会阻塞比较长的时间 在阻塞的这段时间里 无法进行其他工作 这是很不好的 这里是一个同步操作的例子
  • 开发中遇到的线程不安全问题小结

    1 SimpleDateFormat 是线程不安全的 推荐使用如下 1 声明SimpleDateFormat变量时 加synchronized修饰 2 使用DateUtils 工具类 3 使用ThreadLocal 如下 private s
  • linux创建线程失败errno=11

    问 题 为什么一个进程调用pthread create来创建线程 当251次就失败了 失败errno11 Resource temporarily unavailable 原 因 一个进程最多拥有250个线程资源 由于pthread cre
  • 澄清并发编程工具CountDownLatch的误区

    无论你对CountDownLatch的认知是通过看资料还是看博客 很多人都会存在一些误区 现在我也是站在巨人 某些大牛 的肩膀上去总结下这些误区 并把CountDownLatch的含义及用法仔细的演示一遍 1 常见误区 误区一 CountD
  • Calendar类获取月份时月份加一而星期数减一

    Java中Calendar MONTH返回的数值其实是当前月距离第一个月有多少个月份的数值 JANUARY在Java中返回 0 所以我们需要 1 Java 中Calendar DAY OF WEEK中返回的是一周中的第几天 所以他会受到 第
  • Java基础总结之各个模块重要知识点

    一 对象模块 一 初始化 1 对this super 构造函数 构造代码块 静态代码块总结 this 代表当前对象 也就是所在函数所属对象的引用 this对象后面加 调用的是对象的成员变量和方法 this say this对象后面加 调用的
  • C#中的线程(一)入门

    文章系参考转载 英文原文网址请参考 http www albahari com threading 作者 Joseph Albahari 翻译 Swanky Wu 中文翻译作者把原文放在了 google 协作 上面 GFW屏蔽 不能访问和查

随机推荐

  • Linux部署Tomcat无法访问

    前言 环境 RedHat7 0 Tomcat7 JDK7 安装配置成功之后 启动tomcat服务 然后使用本地浏览器访问http xx xx xx xx 8080 无法访问 解决过程 首先怀疑是配置问题 终端输出 echo JAVA HOM
  • Java CSV文件读取、写入及追加工具类

    Java CSV文件读取 写入及追加工具类 追加 FileOutputStream out new FileOutputStream file true 第二个参数true代表追加 CSVUtil java import lombok ex
  • 计算机毕业设计-基于微信小程序高校学生课堂扫码考勤签到系统-校园考勤打卡签到小程序

    注意 该项目只展示部分功能 如需了解 评论区咨询即可 本文目录 1 开发环境 2 系统的设计背景 3 各角色功能模块 3 1 用户 3 2 管理员 4 系统页面展示 4 1 学生端功能模块展示 4 2 教师端功能模块展示 5 更多推荐 6
  • 【JavaScript数据结构与算法】字符串类(计算二进制子串)

    个人简介 个人主页 前端杂货铺 学习方向 主攻前端方向 也会涉及到服务端 Node js 个人状态 在校大学生一枚 已拿多个前端 offer 秋招 未来打算 为中国的工业软件事业效力 n 年 推荐学习 前端面试宝典 Vue2 Vue3 Vu
  • unity之跳一跳(完整版)

    1 场景 1 creat gt 3D objict gt plane 并重命名为ground 做为地面 如下图 2 并调整颜色 大小至适合 如下图 调正颜色 创建material project creat gt Material 用与当作
  • Prometheus热重启

    Prometheus热重启 启动prometheus时 添加参数 web enable lifecycle nohup prometheus web enable lifecycle 然后热重启 dos下执行如下命令 curl XPOST
  • JS 条件判断if语句

    1 流程控制 JS程序一般是按照书写的顺序来执行的 这种运行称为顺序运行 是程序流的默认方向 与顺序运行不同的是另一种的运行将程序流转换到脚本的另外的部分 也就是说不按照程序流运行下一条语句 而是实现别的语句 为了试这个脚本可以使用 这个控
  • mysql-workbench使用中遇到的坑

    1 mysql workbench的安装 问题 1 弹出找不到C 的库 解决 1 的确缺少了这个库 按提示下载一个就好 安装框的左下角 有下载地址 2 有这个库 但就是找不到 可能是win10的问题 win10安装msi的文件需要管理员权限
  • JVM内存JAVA_OPTS参数说明

    JAVA OPTS server Xms2048m Xmx2048m Xss512k server 一定要作为第一个参数 在多个CPU时性能佳 Xms 初始Heap大小 使用的最小内存 cpu性能高时此值应设的大一些 Xmx java he
  • 【Python3爬虫(四)】【urlib.request模块】【ssl认证+cookies(字符串类型转换、session)】

    上一篇 Python3爬虫 三 urlib request模块 cookie Request 开始线 文章目录 一 ssl认证 二 cookies 2 1 字符串类型转换 2 2 session 一 ssl认证 03 requests ss
  • 启动引导:SpringBoot的核心-自动装配(二)

    5 启动引导 SpringBoot的核心 自动装配 二 接前章 6 SpringBoot的自动装配 6 2 Import AutoConfigurationImportSelector class 根据上一章节的基础 看到这个也不难理解 它
  • 去了家新公司,技术总监不让用 IntelliJ IDEA想离职了

    最近有个小伙伴微信和我说 新去的一家公司 技术团队全部规定要用的 Eclipse 开发 技术总监不让用 IntelliJ IDEA 付费也不行 说想离职了 问我该怎么办 首先听到这件事情的时候 我表示十分理解该公司技术总监的决定 虽然我没有
  • 小米6/6X/米8/米9手机刷入鸿蒙HarmonyOS.4.0系统-刷机包下载-遥遥领先

    小米手机除了解锁root权限 刷GSI和第三方ROM也是米粉的一大爱好 这不 在华为发布了HarmonyOS 4 0系统后不久 我们小米用户也成功将自己的手机干山了HarmonyOS 4 0系统 虽然干上去HarmonyOS 4 0系统目前
  • Ubuntu16.04下使用python3,pycharm 安装django

    这里做个总结吧 一直用Ubuntu终端安装django老是安装不上去 我用的是Ubuntu16 04 里面有python2 7 和python3 5两个版本 注意 如果没有更改默认python版本的话 安装pip 直接使用 命令 sudo
  • 修改elementui的导航菜单样式,悬浮、聚焦、失焦,超方便

    最近工作中用到饿了么中的导航菜单 原悬浮背景色不符合要求 且失焦后 无背景色 文档中居然没有给修改这些的接口 急得我要去改源码了 但是吧 看了看源码我又放弃了 我觉得还不如自己重新写 这个问题放了两三天 今天又看了看 突然发现浏览器的一个小
  • 多线程进阶篇Step2

    目录 CAS 乐观锁实现方式之一 CAS操作流程 应用1 使用CAS实现了原子类 AtomicInteger实现i 实现原理 应用2 使用CAS来实现自旋锁 应用3 CAS引发的ABA问题 问题描述 解决办法 引入版本号 synchroni
  • React脚手架搭建项目

    React提供了一个用于创建react项目的脚手架库 create react app 一 项目的搭建 第一步 全局安装 npm i g create react app 第二步 切换到准备创建项目的目录 使用命令 create react
  • $.ajax()方法从服务器获取json数据

    一 什么是json json是一种取代xml的数据结构 和xml相比 它更小巧但描述能力却很强 网络传输数据使用流量更少 速度更快 json就是一串字符串 使用下面的符号标注 键值对 json对象 json数组 双引号内是属性或值 冒号前为
  • 收银系统服务器搭建方法,如何搭建一个小型企业服务器机房?6个步骤学起来!...

    你是否担心依赖第三方在线服务提供商来存储你的业务数据 通过分析传统与云服务器的优势 云服务器真的淘汰了传统服务器 你怎么看 不少企业选择了搭建传统的服务器 并通过内部部署IT解决方案来减少在线数据存储的安全问题 甚至完全避免这些问题 但问题
  • C#中的线程(一)入门

    文章系参考转载 英文原文网址请参考 http www albahari com threading 作者 Joseph Albahari 翻译 Swanky Wu 中文翻译作者把原文放在了 google 协作 上面 GFW屏蔽 不能访问和查