Thứ Sáu, 6 tháng 7, 2012

VÍ DỤ NHỎ VỀ ĐA LUỒNG - MULTITHREADING C#


  1. Sơ qua về luồng (Thread)

  2. Bài viết này mình sẽ hướng dẫn cho các bạn mới tìm hiểu về đa luồng và ứng dụng của nó và được mô tả trên ngôn ngữ C# cho dễ hiểu. Trước tìm hiểu nó cũng vất vả vì nó khó hiểu giờ muốn giúp chút xíu gì đó cho các bạn (Bạn sẽ cảm thấy vô cùng đơn giản nếu làm việc nhiều với nó, đó chỉ là do kỹ năng của mình yếu lên cảm thấy phức tạp)
    Một luồng là một chuỗi liên tiếp những sự thực thi (mã lệnh hay câu lệnh) trong chương trình (ứng dụng). Trong một chương trình C#, dễ thấy việc thực thi được bắt đầu bằng phương thức main() và tiếp tục cho đến khi kết thúc hàm main(). Cấu trúc này rất hay cho những chương trình có một chuỗi xác định những nhiệm vụ liên tiếp, nhưng thường thì trong một chương trình ứng có nhiều hơn một công việc vào cùng một lúc. Một ví dụ rất hay và khá thực tế mà mình thấy trên diễn đàn tin học của "can_qua", các bạn cùng tham khảo


    Vấn đề quan trọng là bạn phải tìm ra một cách nào đó để chia một công việc lớn thành những công việc nhỏ mà trong đó có những việc có thể thực hiện một cách đồng thời. Ví dụ, mẹ giao việc cho con là "làm việc xong mới được đi coi xi-nê". "Công việc lớn" này có thể gồm 3 việc nhỏ "quét nhà", "rửa chén", và đi coi "xi-nê". Trong đó chỉ được "coi xi-nê" sau khi làm xong hai việc kia. Rõ ràng là bạn muốn làm xong việc nhà càng sớm càng tốt để vi vút, nên bạn kêu thằng em bạn quét nhà, bạn thì rửa chén, cả hai người cùng làm đồng thời. Rửa chén xong trước, bạn phải đợi thằng em bạn thông báo là quét nhà cũng xong thì bạn mới vù đi coi xi-nê được. Như vậy multithread cho "rửa chén" và "quét nhà" làm tăng hiệu suất thực hiện công việc của bạn (so với việc bạn làm tuần tự rửa chén, quét nhà, coi xi-nê).
  3. Ví dụ nhỏ về đa luồng
  4. Ví dụ này mình sẽ mô tả 2 luồng (cho đơn giản) được thực thi cùng một lúc.
    Ở đây thì bạn cứ tưởng tượng ra rằng có hai thằng tên là A và B thi đếm từ 0 cho đến 100, thằng nào đếm xong trước thì báo cáo và được về chỗ. Tương ứng mình sẽ tạo ra 2 phương thức A() và B() (mỗi luồng sẽ xử lý một thằng).


          void A()
          {
                 for(int i=0; i<=100; i++)
                 {
                     Console.WriteLine(i.ToString());
                 }
                 Console.WriteLine("A đã đọc xong");   // Báo cáo đã đọc xong
          }
    
          void B()
          {
                 for(int i=0; i<=100; i++)
                 {
                     Console.WriteLine(i.ToString());
                 }
                 Console.WriteLine("B đã đọc xong");   // Báo cáo đã đọc xong
          }
    
    Bây giờ thầy giáo (hoặc là bạn) bỗng cao hứng gọi 2 thằng lên thi đọc --> 2 thằng A và B cùng đọc Đến đây trong phương thức hàm main() của chương trình bạn sẽ phải gọi 2 thằng này

      static void main()
      {
       ThreadStart ts1 = new ThreadStart(A); // Chỉ định thằng A lên đọc
       ThreadStart ts2 = new ThreadStart(B); // Chỉ định thằng B lên đọc
       
       // Sẵn sàng cho cuộc đấu (thi đếm nhanh :D)
       Thread tA = new Thread(ts1);
       Thread tB = new Thread(ts2);
       
       // Bắt đầu bấm giờ
       tA.Start();
       tB.Start();
       tA.Join();
       tB.Join();
       // Hai thằng tranh nhau đếm
       Console.WriteLine("Cuộc thi kết thúc"); // Chờ đến khi 2 thằng đọc xong, không biết thằng nào sẽ thắng :D
       Console.ReadLine();
      }
    
    // Thư viện tham chiếu nằm trong namespace System.Threading;
    // Bạn cần khai báo sử dụng nó using Sytem.Threading;

     
  5. Truyền tham số cho Thread
  6. Có nhiều cách truyền tham số, tuỳ theo nhu cầu mà dùng sao cho phù hợp
    Thông qua phương thức Start(object) thì bạn có thể truyền tham số theo cách này.
    Ví dụ:
       using System;
       using System.Threading;
       class ThreadSample
       {
        public static void Main()
        {
         Thread newThread = new Thread(ThreadSample.DoWork);
         newThread.Start(100); // Dữ liệu truyền vào là một số nguyên 
         
         // Để Start luồng sử dụng phương thức thể hiện (instance method)
         // thì trước tiên ta cần khởi tạo nó trước khi gọi 
         
         ThreadSample worker = new ThreadSample();
         newThread = new Thread(worker.DoMoreWork);
         newThread.Start("Truyền đối tượng cho thread thực thi");
         
         // Nếu biết trước được đối tượng truyền vào thì ta cần ghép kiểu cho nó
         // để việc sử dụng được hiệu quả hơn
        }
        
        public static void DoWork(object data)
        {
         Console.WriteLine("Ðây là luồng tĩnh.");
         Console.WriteLine("Dữ liệu truyền vào: Data = {0}", data);
        }
        
        public void DoMoreWork(object data)
        {
         Console.WriteLine("Đây là luồng cần được khởi tạo");
         Console.WriteLine("Dữ liệu truyền vào là: Data = {0}", data);
        }
    
       }
    Đôi khi ta cũng sử dụng ThreadPool cho việc khởi chạy một luồng mới với tham số là _Param

     ThreadPool.QueueUserWorkItem(new WaitCallback(_ThreadProc), _Param);

    Bạn hãy tham khảo về nó tại đây: http://dotnetperls.com/threadpool

  7. Chờ đợi một luồng khác
  8. Bằng việc sử dụng phương thức Join(); ta có thể cho phép chờ đợi một luồng khác thực hiện xong (để thu thập dữ liệu chẳng hạn - do chia nhỏ công việc mà), thì luồng đã gọi nó mới tiếp tục được công việc của nó
            static void Main(string[] args)
            {
                Console.WriteLine("Main thread: Gọi luồng thứ 2 ThreadProc()...");
    
                Thread t = new Thread(new ThreadStart(ThreadProc));
                t.Start();
    
                for (int i = 0; i < 50; i++)
                {
                    Console.WriteLine("Main thread: Do Some Work.");
                    Thread.Sleep(0);
                }
    
                Console.WriteLine("Main thread finished: And call t.Join()");
                Console.WriteLine("Main thread tạm thời đang được dừng lại");
                t.Join(); // Dừng tại đây
    
                // Sau khi ThreadProc hoàn tất Main thread tiếp tục công việc của nó
                // Tiếp tục thực thi 3 dòng lệnh tiếp theo
                Console.WriteLine("Thread.Join() has returned.");
                Console.WriteLine("Main đã làm xong việc");
                Console.ReadLine();
            }
    
            public static void ThreadProc()
            {
                for (int i = 0; i < 100; i++)
                {
                    Console.WriteLine("ThreadProc: {0}", i);
                    Thread.Sleep(0);
                }
            }
    Đối số cho Join() có thể là int hoặc TimeSpan, khoảng thời gian giới hạn mà Main thread có thể chờ được, ví dụ
    t.Join(10000);
    nghĩa là, sau 10s mà ThreadProc chưa làm xong việc của nó thì Main thread không chờ nữa, tiếp tục công việc khác

Không có nhận xét nào: