Hướng Dẫn Đa Luồng C++ Cho Người Mới Bắt Đầu

Xin chào các bạn, những siêu sao lập trình tương lai! Tôi rất vui được làm hướng dẫn cho các bạn trong hành trình thú vị vào thế giới đa luồng C++. Với những năm dạy lập trình, tôi cam kết rằng mặc dù chủ đề này có thể có vẻ quá khó khăn ban đầu, nhưng thực sự rất thú vị khi các bạn đã quen thuộc với nó. Hãy nhặt tay vào làm việc nào!

C++ Multithreading

Đa Luồng Là Gì?

Trước khi nhảy vào chi tiết, hãy bắt đầu với các khái niệm cơ bản. Hãy tưởng tượng bạn đang ở trong bếp, cố gắng chuẩn bị một bữa ăn phức tạp. Bạn có thể làm mọi thứ một bước tột một - cắt rau, sau đó nấu mì, sau đó chuẩn bị sốt. Nhưng có lẽ nếu bạn có thể làm tất cả các nhiệm vụ này cùng lúc thì sẽ hiệu quả hơn phải không? Đó chính là điều gì đa luồng làm cho các chương trình của chúng ta!

Đa luồng cho phép một chương trình thực hiện nhiều nhiệm vụ đồng thời. Mỗi nhiệm vụ này được gọi là một "luồng". Đó như có nhiều đầu bếp trong bếp, mỗi người chịu trách nhiệm cho một phần khác nhau của bữa ăn.

Bây giờ, hãy khám phá cách chúng ta có thể harnes này trong C++!

Tạo Luồng

Tạo một luồng trong C++ như thuê một đầu bếp mới cho bếp của chúng ta. Chúng ta cần nói cho đầu bếp này (luồng) biết nhiệm vụ nào cần thực hiện. Trong C++, chúng ta làm điều này bằng cách sử dụng lớp std::thread từ thư viện <thread>.

Hãy xem một ví dụ đơn giản:

#include <iostream>
#include <thread>

void nauMì() {
std::cout << "Đang nấu mì..." << std::endl;
}

int main() {
std::thread đầuBếpMột(nauMì);
đầuBếpMột.join();
return 0;
}

Trong ví dụ này:

  1. Chúng ta bao gồm các thư viện cần thiết: <iostream> cho đầu vào/ra và <thread> cho đa luồng.
  2. Chúng ta định nghĩa hàm nauMì() mà luồng của chúng ta sẽ thực thi.
  3. Trong main(), chúng ta tạo một luồng có tên đầuBếpMột và nói cho nó thực thi hàm nauMì().
  4. Chúng ta sử dụng join() để chờ luồng hoàn thành nhiệm vụ trước khi chương trình kết thúc.

Khi bạn chạy chương trình này, bạn sẽ thấy "Đang nấu mì..." in ra console. Chúc mừng! Bạn đã tạo được luồng đầu tiên của mình!

Chấm Dứt Luồng

Bây giờ, giả sử đầu bếp của chúng ta quá lâu để nấu mì? Trong thế giới lập trình, chúng ta có thể cần chấm dứt một luồng trước khi nó hoàn thành nhiệm vụ. Tuy nhiên, điều cần lưu ý là việc buộc dừng luồng có thể dẫn đến rò rỉ tài nguyên và các vấn đề khác. Nói chung, việc thiết kế luồng của bạn để kết thúc tự nhiên hoặc đáp ứng tín hiệu chấm dứt là tốt hơn.

Dưới đây là một ví dụ về cách chúng ta có thể thiết lập một luồng để đáp ứng tín hiệu chấm dứt:

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> dừngLuồng(false);

void nauMì() {
while (!dừngLuồng) {
std::cout << "Vẫn đang nấu mì..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Nấu mì đã dừng!" << std::endl;
}

int main() {
std::thread đầuBếpMột(nauMì);

std::this_thread::sleep_for(std::chrono::seconds(5));
dừngLuồng = true;

đầuBếpMột.join();
return 0;
}

Trong ví dụ này:

  1. Chúng ta sử dụng biến atomic<bool> dừngLuồng để giao tiếp an toàn giữa các luồng.
  2. Hàm nauMì() của chúng ta giờ kiểm tra biến này trong vòng lặp.
  3. Trong main(), chúng ta để luồng chạy trong 5 giây, sau đó đặt dừngLuồng thành true.
  4. Luồng phản hồi bằng cách kết thúc vòng lặp và kết thúc tự nhiên.

Truyền Tham Số Cho Luồng

Giả sử chúng ta muốn đưa cho đầu bếp của mình các hướng dẫn cụ thể hơn? Trong C++, chúng ta có thể truyền tham số vào luồng của mình như truyền tham số vào hàm. Hãy xem cách:

#include <iostream>
#include <thread>
#include <string>

void nauMón(std::string món, int thời_gian) {
std::cout << "Đang nấu " << món << " trong " << thời_gian << " phút." << std::endl;
}

int main() {
std::thread đầuBếpMột(nauMón, "Spaghetti", 10);
std::thread đầuBếpHai(nauMón, "Pizza", 15);

đầuBếpMột.join();
đầuBếpHai.join();

return 0;
}

Trong ví dụ này:

  1. Hàm nauMón() giờ nhận hai tham số: tên món và thời gian nấu.
  2. Chúng ta tạo hai luồng, mỗi luồng nấu một món khác với thời gian khác.
  3. Chúng ta truyền các tham số này trực tiếp khi tạo luồng.

Điều này minh họa sự linh hoạt của luồng - chúng ta có thể có nhiều luồng thực hiện các nhiệm vụ tương tự với các tham số khác nhau!

Kết Luồng và Loại Bỏ Luồng

Cuối cùng, hãy nói về hai khái niệm quan trọng: kết luồng và loại bỏ luồng.

Kết Luồng

Chúng ta đã thấy join() trong các ví dụ trước. Khi chúng ta gọi join() trên một luồng, chúng ta đang nói với chương trình chính của mình để chờ luồng đó hoàn thành trước khi tiếp tục. Đó như chờ đầu bếp hoàn thành chuẩn bị món ăn trước khi phục bữa.

Loại Bỏ Luồng

Đôi khi, chúng ta có thể muốn để luồng chạy độc lập mà không cần chờ nó hoàn thành. Đây là nơi detach() đến. Luồng đã loại bỏ tiếp tục chạy trong nền, ngay cả khi chương trình chính kết thúc.

Dưới đây là một ví dụ minh họa cả hai:

#include <iostream>
#include <thread>
#include <chrono>

void nauCham(std::string món) {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << món << " đã sẵn sàng!" << std::endl;
}

void nauNhanh(std::string món) {
std::cout << món << " đã sẵn sàng!" << std::endl;
}

int main() {
std::thread đầuBếpCham(nauCham, "Stew");
std::thread đầuBếpNhanh(nauNhanh, "Salad");

đầuBếpCham.detach();  // Để đầu bếp cham làm việc trong nền
đầuBếpNhanh.join();   // Chờ đầu bếp nhanh hoàn thành

std::cout << "Chương trình chính kết thúc. Đầu bếp cham có thể vẫn đang làm việc!" << std::endl;
return 0;
}

Trong ví dụ này:

  1. Chúng ta có hai đầu bếp: một đầu bếp cham nấu stew và một đầu bếp nhanh chuẩn bị salad.
  2. Chúng ta loại bỏ luồng của đầu bếp cham, cho phép nó tiếp tục làm việc trong nền.
  3. Chúng ta kết luồng của đầu bếp nhanh, chờ salad sẵn sàng.
  4. Chương trình chính kết thúc, có thể trước khi stew đã sẵn sàng.
Phương Thức Mô Tả Câu Hỏi Sử Dụng
join() Chờ luồng hoàn thành Khi bạn cần kết quả của luồng trước khi tiếp tục
detach() Cho phép luồng chạy độc lập Cho các nhiệm vụ nền có thể chạy một cách độc lập

Và thế là có! Bạn đã bước ra đầu tiên vào thế giới đa luồng C++. Nhớ rằng, như học nấu ăn, việc thành thạo đa luồng cần nhiều tập luyện. Đừng sợ thử nghiệm với các khái niệm này, và sớm bạn sẽ biến ra những chương trình phức tạp, hiệu quả như một đầu bếp cao cấp trong bếp của mã!

Chúc mãi mãi lập trình vui vẻ, và may luồng của bạn luôn chạy suôn sẻ!

Credits: Image by storyset