Concurrent Programming và GCD (Part 1)

brian
6 min readMar 13, 2018

--

Chào mọi người ! :D Từ bữa tới giờ mới có thời gian để viết một bài mới. Vẫn với mục đích cũ, để chia sẻ kiến thức cũng như củng cố kiến thức của bản thân, thì hôm nay mình sẽ tiếp tục series “Tự viết tự đọc”. Nếu ai chưa đọc qua bài viết trước của mình thì có thể xem lại tại đây — Memory Management in iOS.

Chủ đề hôm nay mình muốn đề cập tới cũng là một trong những vấn đề muôn thuở trong lập trình — “Concurrent Programming”, lập trình đồng thời (nghe hơi củ chuối…), hay được biết đến với cái tên khác đó là “Multi-thread Programming”.

Concurrent Programming là gì?

Từ thời xa xưa, khái niệm Concurrent programming đã được hình thành, đây là hành động nói đến việc các tác vụ được thực hiện cùng lúc. Ngay cả từ cái thời mà CPU của chúng ta chỉ có duy nhất 1 core (single-core CPU). Như chúng ta đã biết, thì CPU có 1 core thì tại một thời điểm nó chỉ thực thi được một tác vụ duy nhất. Vậy thì nó concurrent bằng cách nào??? Thật ra thì việc lập trình concurrent trên thiết bị single-core sẽ được thực hiện qua cơ chế gọi là context switch (nếu mình nhớ không nhầm thì khái niệm này mọi người đều đã học trong môn Hệ Điều Hành ở Đại học):v Chắc từ cái tên nó đã nói lên gần hết ý nghĩa. Có nghĩa là lúc CPU chạy, nó sẽ phân bổ thời gian thành các time-slice (lát cắt thời gian :)))) nghe khá chất chơi) và mỗi tác vụ được thực thi trong một đơn vị thời gian là time-slice đó(dạng như đặt timer). Khi timeout, CPU sẽ chuyển qua thực thi tác vụ khác bằng cách switch context, các dữ liệu của tác vụ đang chạy sẽ được lưu trữ lại để có thể gọi lại tiếp tục chạy trong lần tiếp theo (mình sẽ không đi quá chi tiết vào việc dữ liệu sẽ được lưu ở đâu, register, PC,…, cũng như cách CPU vận hành để gọi các tác vụ). Quá trình này diễn ra rất nhanh nên cảm giác của người dùng gần như là các tác vụ được thực hiện đồng thời. Đây cũng là cách để CPU chia sẻ 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ủa công nghệ, các CPU với nhiều cores hơn đã được ra đời. Điều này giúp việc lập trình đồng thời trở nên dễ dàng hơn, cơ chế context switch vẫn được áp dụng, chỉ có điều với multi-core CPU thì trong cùng một thời điểm chúng ta có thể thực thi đồng thời được nhiều tác vụ khác nhau (điều không thể làm được với single-core).

Trên lý thuyết là vậy, nhưng việc lập trình concurrent thật sự không đơn giản. Hay nói cách khác là nó khó vl :))

True story!!!

Process vs Thread

Trước hết, mình sẽ đi vào phân tích các khái niệm cơ bản của lập trình multi-thread. Từ thời đại học, chắc hẳn ai cũng nghe đi nghe lại các khái niệm process và thread. Vậy chúng là gì?

Process có thể hiểu đơn giản là một chương trình hay một ứng dụng đang chạy, trong một process có thể có một hoặc nhiều thread. Nếu process chỉ có một thread thì thread đó được gọi là main thread. Vậy process và thread có gì khác nhau:

  • Mỗi process có không gian bộ nhớ (address spaces, để lưu trữ code, data và các resource), môi trường thực thi riêng biệt.
  • Process được kiểm soát hoàn toàn bởi OS.
  • Các threads trong cùng một process có thể chia sẻ không gian bộ nhớ của process cho nhau (nói chuyện với nhau).
  • Mỗi thread có một stack, program counter (PC) và register riêng biệt.
Các threads chia sẻ vùng nhớ của process.
Mỗi thread có stack, PC và register riêng biệt.

Dựa trên các đặc điểm trên ta có thể dễ hiểu tại sao người ta chọn sử dụng multi-threading thay vì multi-processing. Việc sử dụng nhiều threads tiết kiệm bộ nhớ cho OS hơn thay vì nhiều processes và việc quản lý các threads, cho các threads giao tiếp với nhau cũng dễ dàng hơn nhiều so với processes, phụ thuộc vào OS, kernel,…

Các khái niệm/thuật ngữ quan trọng

Critical section: là một đoạn code chỉ được thực thi duy nhất bởi một thread nào đó tại một thời điểm. Nếu có nhiều hơn một thread thực thi đoạn code này sẽ xảy ra lỗi. Ví dụ đó có thể là đoạn code truy cập tới các shared resources như file, data, global variables,…

Race condition: là khi nhiều threads access vào một shared source mà không đảm bảo rằng thread này kết thúc thực thi trên dữ liệu trước khi thread kia truy cập dữ liệu. Nghĩa là các threads đồng thời cùng đọc và cùng ghi trên dữ liệu. Có thể hiểu race condition là trường hợp sẽ xảy ra nếu không quản lý tốt critical section. Ví dụ như có một biến integer a = 10, trong cùng một thời điểm, thread 1 lấy ra a và tăng a lên 1 (a = 11), thread 2 cũng lấy ra a và tăng a lên 2 (a = 12) => Dữ liệu sẽ không đảm bảo tính đúng đắn.

Deadlock: là hiện tượng hai hay nhiều tác vụ phải chờ đợi lẫn nhau để hoàn thành. Ví dụ:

func doSomething() {
doNothing()
}
func doNothing() {
doSomething()
}

Trong ví dụ trên, nếu thread 1 đang chạy hàm doSomething() và thread 2 chạy hàm doNothing(), thì 2 threads sẽ bị phụ thuộc lẫn nhau. Thread 1 cần thread 2 chạy xong doNothing() để kết thúc, trong khi thread 2 lại cần thread 1 chạy xong doSomething().

Thread safe: một code được gọi là thread safe code khi trong môi trường multi-threading, nó được thực thi mà không xảy ra bất cứ lỗi nào.

Atomicity: một tác vụ hoặc một công việc cụ thể được cho là atomic khi nó không thể bị gián đoạn. Có nghĩa là nó được đảm bảo sẽ được hoàn thành, không có trường hợp trả ra một trạng thái không hợp lệ nào đó (lỗi). Đây cũng là một ví dụ của thread safe.

Thôi tới đây bài viết cũng đã đủ dài rồi :)) Trong phần tiếp theo mình sẽ nói về một số công nghệ được sử dụng để lập trình multi-threading, cụ thể là đối với iOS. Cũng như lần trước thì mình xin cảm ơn các bạn đã dành thời gian để đọc hết những dòng mình viết ra. Hẹn mọi người vào bài viết sau. Nếu có bất cứ sai sót gì trong bài thì mọi người hãy comment để cho mình biết và chỉnh sửa nhé ❤

Đã update PART 2

--

--

brian

a software engineer who does software engineering