Chào các bạn! 😀 Hiện tại, chỉ còn thời gian để viết một bài mới. Với mục tiêu vẫn là chia sẻ kiến thức cũng như củng cố kiến thức, hôm nay mình sẽ tiếp tục series “Tự viết bài”. Nếu bạn chưa đọc bài viết trước của tôi, bạn có thể xem lại tại đây – Quản lý bộ nhớ trong iOS. Chủ đề hôm nay mình muốn nói đến cũng là một trong những vấn đề lâu đời trong lập trình – “Lập trình đồng thời” hay còn gọi là “Lập trình đa luồng”.
Lập trình đồng thời là gì?
Từ thời xa xưa, khái niệm về lập trình đồng thời đã được hình thành. Nó ám chỉ việc thực hiện các nhiệm vụ cùng một lúc. Dù CPU chỉ có một lõi (CPU lõi đơn) như chúng ta biết, nó chỉ có thể thực hiện một tác vụ duy nhất tại một thời điểm. Vậy làm sao để lập trình đồng thời? Thực tế, trên các thiết bị lõi đơn, việc này được thực hiện thông qua cơ chế gọi là chuyển đổi ngữ cảnh (nếu mình không nhầm, từ này ai cũng học trong Hệ quản lý ở trường Đại học). Giả sử rằng tên của nó đã giải thích phần lớn ý nghĩa. Khi CPU hoạt động, nó sẽ phân bổ các khoảng thời gian thành các “lát” (lược đồ thời gian) và mỗi tác vụ được thực hiện trong mỗi khoảng thời gian được xem như là một “lát”. Khi kết thúc khoảng thời gian, CPU sẽ chuyển sang việc thực hiện một tác vụ khác bằng cách chuyển ngữ cảnh.
Dữ liệu của tác vụ đang chạy sẽ được lưu trữ để có thể tiếp tục chạy trong các lần gọi sau (mình không đi sâu vào chi tiết quá nhiều). Ví dụ, thanh ghi, PC, và cách hoạt động của CPU để gọi các tác vụ. Quá trình này diễn ra rất nhanh chóng, tạo cảm giác như các tác vụ đang được thực hiện cùng một lúc. Đây cũng là cách để CPU phân bổ tài nguyên phần cứng cho nhiều tác vụ khác nhau. Sau này, với sự phát triển công nghệ, CPU đã có hơn một lõi. Điều này đã thuận lợi cho việc lập trình đồng thời hơn và cơ chế chuyển ngữ cảnh vẫn được áp dụng. Với CPU đa nhân, chúng ta có thể thực hiện nhiều tác vụ khác nhau đồng thời (điều không thể làm được với lõi đơn).
Xử lý so với Chủ đề
Trước hết, tôi sẽ đi vào phân tích các khái niệm cơ bản của lập trình đa luồng. Kể từ lúc học đại học, chắc hẳn người nào cũng đã nghe qua các khái niệm về thứ tự và luồng. Vậy chúng là gì?
Process có thể hiểu đơn giản là một chương trình hoặc ứng dụng đang chạy, trong một tiến trình có thể có một hoặc nhiều luồng. Nếu tiến trình chỉ có một luồng, luồng đó được gọi là luồng chính. Vậy sự khác lạ giữa process và thread là gì:
Mỗi tiến trình có ko gian bộ nhớ riêng (ko gian địa chỉ, để lưu trữ mã, dữ liệu và tài nguyên), môi trường thực thi riêng lẻ. Quá trình hoàn toàn được kiểm soát bởi hệ quản lý. Các chủ đề trong cùng một thứ tự có thể san sớt ko gian. xử lý bộ nhớ cho nhau (nói chuyện với nhau). Mỗi luồng có một ngăn xếp, bộ đếm chương trình (PC) và thanh ghi riêng lẻ.
Dựa vào các đặc điểm trên, dễ hiểu vì sao người ta lại chọn sử dụng đa luồng hơn đa xử lý. Sử dụng nhiều luồng giúp tiết kiệm bộ nhớ hơn cho HĐH thay vì nhiều tiến trình và quản lý các luồng, để các luồng giao tiếp với nhau cũng dễ dàng hơn nhiều so với các thứ tự, tùy thuộc vào OS, kernel, v.v.
Các khái niệm / thuật ngữ quan trọng
Phần quan trọng: Đây là đoạn mã chỉ có thể được thực thi bởi một luồng duy nhất tại một thời điểm. Nếu có nhiều luồng cùng thực thi mã này, lỗi sẽ xảy ra. Ví dụ: Đây có thể là đoạn mã truy cập vào các tài nguyên như tệp tin, dữ liệu, biến toàn cục, vv.
Điều kiện chạy đua: Đây là khi nhiều luồng cùng truy cập vào một tài nguyên nhưng không đảm bảo rằng một luồng hoàn thành việc thực thi trên dữ liệu trước khi luồng khác truy cập vào nó. Tức là, các luồng đọc và ghi dữ liệu cùng một lúc. Có thể hiểu điều kiện chạy đua là tình huống xảy ra khi phần quan trọng không được quản lý cẩn thận. Ví dụ, giả sử có một biến số nguyên a = 10 và hai luồng lấy a và tăng giá trị của a lên 1 (a = 11) và 2 (a = 12) tương ứng. Kết quả là dữ liệu không được đảm bảo là chính xác.
Tính hết sức: Đây là hiện tượng khi hai hoặc nhiều tác vụ phải chờ đợi cho nhau hoàn thành. Ví dụ: func doSomething() {doNothing()} func doNothing() {doSomething()}. Trong ví dụ này, nếu luồng 1 đang thực thi hàm doSomething () và luồng 2 đang thực thi hàm doNothing (), hai luồng này sẽ phụ thuộc lẫn nhau. Luồng 1 cần luồng 2 để kết thúc doNothing () để kết thúc, trong khi luồng 2 cần luồng 1 để kết thúc doSomething ().
An toàn chủ đề: Mã được gọi là an toàn chủ đề khi trong môi trường đa luồng, nó có thể được thực thi mà không gây ra bất kỳ lỗi nào.
Tính nguyên tử: Một tác vụ hoặc công việc được xem là nguyên tử khi nó không thể bị gián đoạn. Điều này có nghĩa rằng tác vụ được đảm bảo hoàn thành mà không trở thành trạng thái không hợp lệ (lỗi). Đây cũng là một ví dụ về tính an toàn chủ đề.
Chà, bài viết đã khá dài rồi :)) Trong phần tiếp theo, tôi sẽ nói về một số công nghệ được sử dụng để lập trình đa luồng, đặc biệt là cho iOS. Như thường lệ, tôi xin cảm ơn bạn đã dành thời gian để đọc những gì tôi viết. Hẹn gặp lại trong những bài viết tiếp theo. Nếu có bất kỳ sai sót nào trong bài viết, hãy để lại comment để tôi biết và sửa chúng nhé ❤
Nguồn: thtrangdai.edu.vn
Chuyên mục: Blog