Lập trình

Vấn đề cơ bản khi học lập trình .NET

Huy Erick

Bài viết này mô tả vài khía cạnh, vài khái niệm quan trọng cho những người bắt đầu học ngôn ngữ lập trình .NET như C#, VB, v.v. Có thể hình dung bài viết như...

Bài viết này mô tả vài khía cạnh, vài khái niệm quan trọng cho những người bắt đầu học ngôn ngữ lập trình .NET như C#, VB, v.v. Có thể hình dung bài viết như một tấm bản đồ đơn giản để người học có thể hình dung sơ lượt về những vấn đề cơ bản mà mình sẽ gặp khi học một ngôn ngữ lập trình .NET cụ thể.

Multiprocessing

Multitasking (đa nhiệm vụ)

Multitasking là khả năng thực hiện nhiều nhiệm vụ nhưng chỉ một nhiệm vụ tại một thời điểm và các nhiệm vụ khác phải dừng để chờ nhiệm vụ này hoàn tất. Hệ điều hành chuyển các nhiệm vụ với tốc độ rất nhanh làm người dùng có cảm giác các nhiệm vụ này được thực hiện đồng thời.

Multiprocessing (đa tiến trình)

Tiến trình (process) hay nhiệm vụ (task) có thể được hiểu giống nhau, nhưng trong hệ thống dùng đa tiến trình thì nhiều nhiệm vụ hay tiến trình được xử lý tại cùng thời điểm hay đồng thời. Ngày nay, với sự phát triển của các bộ xử lý đa lõi (multicore) tức nhiều lõi trên cùng một bộ xử lý đã hỗ trợ sức mạnh máy tính thực hiện đa tiến trình hiệu quả hơn.

Multithreading (đa tiểu trình)

Một tiến trình (process) là một thể hiện (instance) của một chương trình, như vậy một chương trình có thể có nhiều tiến trình ví dụ có thể mở đồng thời nhiều cửa sổ Notepad hay trình duyệt web. Một tiểu trình (thread) là các lệnh thực thi trong một tiến trình và có thể thực hiện song song với các tiểu trình (hay các lệnh) khác. Thỉnh thoảng chúng ta cần thực hiện nhiều tiểu trình đồng thời trong cùng một tiến trình, mỗi tiểu trình có thể dùng mã chương trình mà không ảnh hưởng đến tiểu trình khác. Cơ chế này được gọi là đa tiểu trình.

Các vấn đề với cơ chế song song (paralleism)

Khi thực hiện nhiều tiểu trình song song, chúng ta có thể bắt gặp các vấn đề như tranh chấp tài nguyên (contention for resources), điều kiện tương tranh (race conditions), và khoá chết (deadlock).

Tranh chấp tài nguyên

Khi thực hiện song song, thỉnh thoảng các tiểu trình cần sử dụng chung tài nguyên như cùng truy cập đến ổ đĩa cứng, CD hay DVD, hay các tài nguyên khác. Vấn đề tranh chấp tài nguyên có thể dẫn tới nhiều ứng xử không như mong đợi, ví dụ như vấn đề điều kiện tương tranh.

Điều kiện tương tranh

Trong quá trình thực hiện song song, khi kết quả tính toán phụ thuộc vào việc tính toán của nhiều tiểu trình thì xuất hiện điều kiện tương tranh.

Ví dụ chúng ta muốn tính tổng của 2 triệu số. Chúng ta có thể lặp qua các số và cộng dồn mỗi số một lần, hay để tiết kiệm thời gian chúng ta dùng cơ chế đa tiểu trình bằng cách tách thành hai tiểu trình - mỗi tiểu trình tính tổng 1 triệu số - thực hiện song song. Thuật toán của mỗi tiểu trình như sau:

for i in range(start, finish+1):     total += i   # (1)  # (2) Kết thúc tính tổng của tiểu trình

Hai tiểu trình có cùng thuật toán chỉ khác giá trị khởi đầu start và giá trị kết thúc finish cho vòng lặp for. Tiểu trình đầu có start =1finish = 1000000, tiểu trình thứ hai có start = 1000001finish = 2000000.

Nếu hai tiểu trình (tạm gọi là Thread 1 và Thread 2) thực hiện tại cùng một thời điểm thì sẽ xuất hiện điều kiện tương tranh. Ví dụ:

total = 100   # Giả sử tổng ban đầu là 100  # Tiểu trình Thread 1 total += 20   # (3) # Kết quả: total = 120  # Tiểu trình Thread 2 total += 30   # (4) # Kết quả: total = 130 (thay vì 150 như mong đợi)

Một cách để ngăn chặn điều kiện tương tranh là dùng khoá (lock).

Khoá (lock)

Dùng khoá để đảm bảo một tiểu trình truy cập độc quyền đến mã, bộ nhớ, hay các tài nguyên khác nhằm ngăn chặn điều kiện tương tranh hay lỗi truy cập. Trong ví dụ trên, chúng ta có thể dùng khoá để truy cập độc quyền đến biến total trong khi tính toán. Đoạn mã trên có thể viết lại:

lock(total):     for i in range(start, finish+1):         total += i   # (1)  # (2) Kết thúc tính tổng của tiểu trình

Bây giờ, nếu Thread 1 đang dùng total thì Thread 2 không thể dùng nó và phải chờ cho đến khi Thread 1 giải phóng khoá.

Tuy nhiên, dùng khoá có thể làm cho chương trình chậm hơn do phụ thuộc vào thời gian giải phóng của các tiểu trình và có thể dẫn đến tình huống không như mong đợi - tình huống khoá chết (deadlock).

Khoá chết (deadlock)

Khoá chết xuất hiện khi cả hai tiểu trình cùng chờ tài nguyên bị chiếm giữ bởi mỗi bên, ví dụ Thread 1 chiếm giữ tài nguyên A (bị khoá) và chờ tài nguyên B, và Thread 2 chiếm giữ tài nguyên B (bị khoá) và chờ tài nguyên A.

Việc phát hiện và phá vỡ khoá chết là khó khăn trong cơ chế đa tiểu trình.

Task Parallel Library

Microsoft cung cấp một thư viện (Task Parallel Library hay gọi tắt là TPL) các công cụ để tạo các tiểu trình chạy song song dùng trong các ứng dụng .NET.

Một vài công cụ chính được cung cấp bởi TPL:

  • Invoke: thực hiện vài đoạn mã đồng thời.
  • For: thực hiện cùng một đoạn mã với số các tham số khác nhau vài lần theo cơ chế song song.
  • ForEach: thực hiện cùng một đoạn mã với số các tham số khác nhau vài lần theo cơ chế song song.

Môi trường lập trình

Từ phần mềm (software) đến phần cứng (hardware)

Bộ xử lý máy tính chỉ hiểu được các bit (0 hay 1). Việc viết bằng ngôn ngữ máy (dùng bit 0 hay 1) là rất khó khăn cho con người nên các nhà lập trình đã dùng các ngôn ngữ cấp cao để viết chương trình. Ngôn đầu tiên được phát triển là ngôn ngữ Assembly dễ đọc hơn với con người.

Mặc dù dễ đọc hơn ngôn ngữ máy nhưng ngôn ngữ Assembly vẫn còn khó để viết hay sửa lỗi chương trình.

Các ngôn ngữ cấp cao như Fortran, Pascal, C++ được phát triển với các câu lệnh phức tạp làm việc ở mức trừu tượng cao hơn so với ngôn ngữ Assembly. Một trình biên dịch (compiler) được dùng để chuyển chương trình mức cao sang ngôn ngữ máy để bộ xử lý máy tính có thể thực hiện lệnh.

Một vài ngôn ngữ như Java, C#, hay Visual Basic, thêm một bước khác vào trong quá trình chuyển sang ngôn ngữ máy để tăng tính di động (portable) cho chương trình. Thay vì biên dịch trực tiếp đến ngôn ngữ máy, mã chương trình được chuyển thành ngôn ngữ trung gian, thỉnh thoảng được gọi là bytecode. Sau đó, tại thời điểm chạy chương trình (run time), mã trung gian sẽ được biên dịch thành mã máy để thực thi. Bước chuyển từ mã trung gian đến mã máy còn được gọi là biên dịch Just-in-time (JIT).

Đến thời điểm này chúng ta cần phân biệt 3 khái niệm quan trọng:

  • Run time: thời điểm chương trình đang chạy.
  • Design time: thời điểm thiết kế hay xây dựng chương trình (viết mã, thiết kế giao diện, v.v.).
  • Biên dịch Just-in-time: bước chuyển từ mã trung gian đến mã máy.

Trong các ngôn ngữ .NET như C# hay VB, ngôn ngữ trung gian được gọi là Common Intermediate Language (CIL) và thành phần chuyển ngôn ngữ trung gian sang ngôn ngữ máy được gọi là Common Language Runtime (CLR).

Trong Java, ngôn ngữ trung gian gọi là Java bytecode và thành phần chuyển ngôn ngữ trung gian sang ngôn ngữ máy gọi là Java Virtual Machine (JVM).

Môi trường lập trình

Để chương trình có thể được viết, kiểm tra, hay sửa lỗi dễ dàng, cần một môi trường lập trình chứa nhiều công cụ hữu ích còn được gọi là Môi trường phát triển tích hợp (Integrated Development Environment - IDE) như:

  • Code Editor: dùng để soạn thảo mã chương trình.
  • Debugger: dùng để phát hiện, chỉnh sửa lỗi chương trình.
  • Compiler: biên dịch chương trình thành ngôn ngữ trung gian hay máy.
  • Build automation tools: cho phép biên dịch tuỳ chọn.
  • Testing tools: kiểm tra chương trình.
  • Source code management tools: quản lý mã chương trình.
  • Object-oriented tools: tổ chức các lớp để dễ hiểu, dễ quản lý hơn.

Visual Studio

Visual Studio là IDE của Microsoft hỗ trợ các ngôn ngữ như C#, Visual Basic, C/C++, F#, v.v. Một vài đặc trưng cơ bản được cung cấp bởi Visual Studio IDE:

  • Customizable menus and toolbars: các thực đơn và thanh công cụ tuỳ chọn.
  • Customizable windows: các cửa sổ tuỳ chọn.
  • Auto hiding windows: các cửa sổ ẩn tự động.
  • IntelliSense: hỗ trợ cho viết mã chương trình, dùng để phân biệt hoa thường.
  • Call stack: hiển thị một loạt các hàm gọi để dẫn đến điểm thực thi hiện tại.
  • Sequence diagrams and dependency graphs: cung cấp trong các phiên bản Pro của Visual Studio dùng để cung cấp thông tin về cách các hàm gọi các hàm khác.

Các thành phần của chương trình Windows

Thực đơn (menu)

Một trong những thành phần quan trọng nhất trong một chương trình Windows là các thực đơn (menu). Một vài khía cạnh quan trọng:

  • Các phím kết hợp (accelerators): là các phím kết hợp giữa các phím điều khiển như Alt, Ctrl, v.v. dùng để điều hướng đến các mục thực đơn nhanh hơn, ví dụ tổ hợp phím Alt + F để điều hướng đến File.
  • Các phím tắt (shortcut): là các phím tắt thực hiện ngay một lệnh của một mục thực đơn nào đó. Bởi vì một shortcut thực hiện trực tiếp một lệnh nên không có hai lệnh nào có cùng một shortcut. Các phím điều khiển có thể dùng trong shortcut là Alt, Shift, và Ctrl.
  • Các mục thực đơn chuẩn: thường thấy trong trình soạn thảo Word, Notepad, v.v. như File, New, Open, Save, Save As, v.v.
  • Một vài nguyên tắc khi thiết kế thực đơn:
    • Không nên ẩn các lệnh không cần dùng mà nên vô hiệu hoá nó (hay làm mờ) để người dùng hiểu rằng nó không được dùng thời điểm này nhưng sẽ được dùng lúc khác.

1