Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 1 Lớp : K51CHTTT
LỜI CẢM ƠN
Lời đầu tiên tôi xin được gửi lời cảm ơn chân thành tới các thầy cô giáo Trường Đại
Học Công Nghệ - Đại Học Quốc Gia Hà Nội, đặc biệt là các thầy cô trong khoa Công
Nghệ Thông Tin, những người đã trực tiếp chỉ bảo tôi những kiến thức trong suốt bốn
năm học vừa qua trên ghế giảng đường.
Đặc biệt tôi xin được bày tỏ lòng kính trọng và biết ơn tới TS. Nguyễn Hả
i Châu
người đã trực tiếp hướng dẫn, giúp đỡ tôi hoàn thành khóa luận này.
Xin được gửi lời chúc sức khỏe và hạnh phúc tới tất cả các thầy cô. Xin chúc thầy cô
đạt được nhiều thành tựu hơn nữa trong sự nghiệp đào tạo tri thức cho đất nước cũng như
trong các công việc nghiên cứu khoa học.
Trân trọng cảm ơn!
Hà Nội, Ngày 20 Tháng 05 năm 2010
Sinh viên thực hiện
Cấn Việt D
ũng
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 2 Lớp : K51CHTTT
TÓM TẮT KHÓA LUẬN
Trong khóa luận này, tôi sẽ trình bày lý thuyết về Thread, lập trình posix thread
và cài đặt một bài toán sử dụng posix thread để thấy được hiệu quả của việc sử dụng
chúng. Phần đầu tiên,tôi giới thiêu lý thuyết về Thread, Multi thread và các vấn đề
liên quan. Tiếp theo, tôi trình bày về lập trình posix thread trên hệ điều hành linux,
bao gồm cách tạo, ngắt thread và đồng bộ các thread trong chương trình. Cuối cùng,
là cách cài đặt bài toán “tìm cặp điểm gần nhau nhất trong tập N điểm cho trướ
c”( qui
ước bài toán này là bài toán closest_pair) sử dụng posix thread trên ngôn ngữ C để
thấy được sự hiệu quả của việc sử dụng multithread trong việc nâng cao hiệu năng
của chương trình, ở đây cụ thể là tốc độ tính toán tăng lên rõ rệt.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 3 Lớp : K51CHTTT
MỤC LỤC
CHƯƠNG 1: GIỚI THIỆU VỀ THREAD VÀ MULTI THREAD.....................................7
1.1. Tổng quan về thread ....................................................................................................7
1.2. So sánh thread với tiến trình........................................................................................7
1.3. Đa thread: những lợi thế ..............................................................................................8
1.4. Tiến trình, thread nhân, thread người dùng, fiber........................................................9
1.5. Vấn đề đưa ra của thread và fiber..............................................................................10
1.5.1.Truy cập đồng thời và cấu trúc dữ liệu
............................................................................. 10
1.5.2.Vào/ ra và bộ lập lịch
............................................................................................................ 11
1.6. Các mô hình...............................................................................................................12
1.6.1.
Mô hình 1:1 (thread cấp nhân)
........................................................................................... 12
1.6.2.
Mô hình N:1 (thread cấp người dùng)
............................................................................. 12
1.6.3.
Mô hình N:M (thread tích hợp)
......................................................................................... 12
1.7. Ngôn ngữ hỗ trợ.........................................................................................................13
CHƯƠNG 2: POSIX THREAD PROGRAMMING .........................................................14
2.1. Tổng quan về Pthread ................................................................................................14
2.1.1.
Khái niệm Pthread
................................................................................................................. 14
2.1.2.
Tại sao lại sử dụng Pthread?
............................................................................................... 14
2.1.3.
Pthread API
............................................................................................................................. 16
2.1.4.
Biên dịch chương trình Threaded
..................................................................................... 17
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 4 Lớp : K51CHTTT
2.2. Quản lý Thread ..........................................................................................................18
2.2.1.
Các thủ tục chính
................................................................................................................... 18
2.2.2.
Tạo Thread
.............................................................................................................................. 18
2.2.3.
Thiết lập các thuộc tính cho Thread
................................................................................. 19
2.2.4.
Hủy thread
............................................................................................................................... 19
2.2.5.
Truyền tham số cho Thread
................................................................................................ 21
2.2.6.
Nối và tách Thread
................................................................................................................ 22
2.2.6.1. Những thủ tục chính ..........................................................................................23
2.2.6.2. Nối Thread.........................................................................................................23
2.2.6.3. Có thể nối được hay không?..............................................................................23
2.2.6.4. Tách (detaching)................................................................................................24
2.2.7.
Quản lý stack
.......................................................................................................................... 26
2.2.7.1. Những thủ tục ....................................................................................................26
2.2.7.2. Ngăn ngừa những vấn đề với stack ...................................................................26
2.3. Biến Mutex ................................................................................................................26
2.3.1.
Khái niệm mutex
................................................................................................................... 26
2.3.2.
Tạo ra và phá hủy mutex
..................................................................................................... 27
2.3.2.1. Những thủ tục ....................................................................................................27
2.3.2.2. Cách sử dụng .....................................................................................................28
2.3.3.
Khóa và mở khóa mutex
...................................................................................................... 28
2.3.3.1. Các thủ tục.........................................................................................................28
2.3.3.2. Cách sử dụng .....................................................................................................28
2.4. Biến điều kiện ............................................................................................................33
2.4.1.
Khái niệm về biến điều kiện
............................................................................................... 33
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 5 Lớp : K51CHTTT
2.4.2. Tạo ra và phá hủy 1 biến điều kiện
.................................................................................. 35
2.4.2.1. Các thủ tục.........................................................................................................35
2.4.2.2. Cách sử dụng .....................................................................................................35
2.4.3. Waiting và signaling trên biến điều kiện
........................................................................ 35
2.4.3.1. Các thủ tục.........................................................................................................36
2.4.3.2. Cách sử dụng .....................................................................................................36
2.5. Dữ liệu riêng của Thread(Thread – specific data).....................................................39
2.5.1.
Khái niệm dữ liệu riêng của thread
.................................................................................. 39
2.5.2. Cấp phát dữ liệu riêng của thread
..................................................................................... 39
2.5.3. Truy cập vào dữ liệu riêng của thread
............................................................................. 40
2.5.4.Xóa dữ liệu trong thread
....................................................................................................... 42
CHƯƠNG 3: BÀI TOÁN CLOSEST_PAIR TRONG KHÔNG GIAN HAI CHIỀU SỬ
DỤNG MULTITHREADING...................................................................................43
3.1. Giới thiệu bài toán .....................................................................................................43
3.2. Các thuật toán khác nhau để giải bài toán tìm khoảng cách ngắn nhất giữa các cặp
điểm trong N điểm cho trước.....................................................................................43
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 6 Lớp : K51CHTTT
GIỚI THIỆU
Thread là một mô hình lập trình phổ biến cho phép nhiều thread đơn có thể
chạy trên cùng một tiến trình, và các thread này có thể chia sẻ tài nguyên của tiến
trình cũng như có thể tính toán độc lập. Và ứng dụng hữu ích nhất của mô hình này
là khi nó được áp dụng cho một tiến trình đơn lẻ để cho phép tính toán song song
trên một hệ thống đa xử lý. Trong khóa luận này, tôi sẽ trình bày mô hình này trên
chuẩn IEEE POSIX 1003.1c, được gọi là POSIX thread hay Pthread. Lý do tôi chọn
Pthread, là để nhận ra hiệu quả ti
ềm năng của chương trình, việc tạo ra một thread
sử dụng ít tài nguyên và chi phí của hệ điều hành hơn rất nhiều so với việc tạo ra
một tiến trình.
Nội dung chính của khóa luận bao gồm 3 chương, nội dung cụ thể như sau:
Chương I: Giới thiệu về thread và multi thread. Chương này tập trung giới thiệu
về thread và multi thread, so sánh giữa thread với tiến trình và cùng với đó là những
lợi th
ế khi sử dụng multi thread. Cuối cùng là các mô hình thread và các ngôn ngữ
hỗ trợ.
Chương II: Lập trình POSIX thread. Chương này sẽ đề cập tới các vấn đề cơ bản
trong lập trình POSIX thread (Pthread). Các vấn đề được đề cập bao gồm việc quản
lý thread, tạo, hủy, tách và nối thread. Các biến mutex, biến điều kiện và cách sử
dụng. Mỗi phần đều có những ví dụ minh họa.
Chương III: Bài toán closest_pair. Chương này tôi sẽ cài
đặt bài toán closest_pair
hai chiều bằng các phương pháp thông thường, đệ qui và đệ qui sử dụng multi thread
để thấy được hiệu quả của việc sử dụng multi thread.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 7 Lớp : K51CHTTT
CHƯƠNG 1: GIỚI THIỆU VỀ THREAD VÀ MULTI THREAD
1.1. Tổng quan về thread
Trong khoa học máy tính, một chuỗi các tính toán kết quả từ một fork của một
chương trình máy tính chia thành hai hoặc nhiều nhiệm vụ chạy đồng thời. Sự thể
hiện của các thread và tiến trình (process) là khác nhau trong một hệ điều hành,
nhưng trong hầu hết các trường hợp, một thread được nằm bên trong một tiến trình.
Nhiều thread có thể tồn tại cùng trong một tiến trình và chia sẻ tài nguyên như bộ
nhớ, trong khi những tiến trình khác nhau không thế chia sẻ tài nguyên.
Trên một bộ xử lý đơn, multi thread thường xảy ra bởi sự phân chia thời gian
ghép (như trong multitasking): bộ xử lý chuyển giữa những thread khác nhau. Ngữ
cảnh chuyển thường xảy ra một cách thường xuyên đủ để người dùng nhận thấy
được nhiều thread hoặc nhiệm vụ đang chạy tại cùng một thời điểm. Trên một bộ đa
xử
lý hoặc hệ thống nhiều nhân, những thread hoặc nhiệm vụ sẽ chạy cùng lúc, với
mỗi một bộ xử lý hoặc nhân chạy một thread hoặc nhiệm vụ riêng.
Nhiều hệ điều hành hiện đại hỗ trợ trực tiếp sự phân chia thời gian hoặc đa
thread với một bộ lập lịch tiến trình. Nhân của hệ điều hành cho phép người lập trình
tính toán thread bằng các giao di
ện lời gọi hệ thống. Một vài thể hiện được gọi là
thread nhân, trong khi một tiến trình nhẹ (lightweight process) là một kiểu xác định
của thread nhân để chia sẻ trạng thái và thông tin.
Chương trình có thể có thread không gian người dùng khi lập trình thread với
thời gian, tín hiệu hoặc những phương thức khác để làm gián đoạn thực hiện riêng
của họ để thực hiện một sắp xếp ad-hoc hoặc chia thời gian.
1.2. So sánh thread vớ
i tiến trình
Thread khác với tiến trình trong hệ điều hành đa nhiệm truyền thống ở các
điểm sau:
- Các tiến trình thường được độc lập, còn các thread thì tồn tại như là các tập
con của một tiến trình.
- Tiến trình có trạng thái thông tin đáng kể, trong khi nhiều thread trong một
tiến trình chia sẽ trạng thái tiến trình cũng như bộ nhớ và các tài nguyên khác.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 8 Lớp : K51CHTTT
- Tiến trình có vùng địa chỉ riêng biệt, trong khi thread chia sẽ không gian địa
chỉ của chúng.
- Các tiến trình chỉ tương tác thông qua cơ chế liên tiến trình do hệ thống cung
cấp.
- Ngữ cảnh chuyển giữa các thread trong cùng một tiến trình thường sẽ nhanh
hơn chuyển giữa các tiến trình.
1.3. Đa thread: những lợi thế
Multi thread như là một mô hình lập trình phổ biến và cho phép thực hiện
nhiều thread tồn tạ
i trong một tiến trình đơn. Những thread này chia sẻ tài nguyên
của tiến trình nhưng cũng có thể tính toán độc lập. Mô hình lập trình thread cung cấp
cho người phát triển những sự hữu ích của việc tính toán đồng thời. Tuy nhiên, có lẽ
ứng dụng thú vị nhât cho công nghệ này là khi nó được áp dụng cho một tiến trình
đơn lẻ để cho phép tính toán song song trên một hệ thống đa xử lý. Lợi thế này của
lập trình multi thread cho phép nó tính toán nhanh hơn trên hệ thống máy tính có
nhiều CPU, CPU với nhiều nhân hoặc qua một cụm máy – bởi vì những thread của
chương trình cho vay chính bản thân nó để thực hiện sự đồng thời. Trong trường
hợp này, người lập trình cần phải cẩn thận để tránh lỗi chạy điều kiện (race
condition), và những đối xử không thuộc trực giác. Để các dữ liệu được thao tác
chính xác, những thread sẽ quy thời gian để xử lý dữ liệu theo thứ t
ự đúng. Thread
có thể yêu cầu hoạt động độc quyền (thường được thực hiện bằng cách sử dụng
semaphore) để ngăn chặn dữ liệu không bị đồng thời sửa đổi hoặc đọc trong khi quá
trình đang bị sửa đổi.
Một lợi thế khác của multi thread, kể cả đối với hệ thống đơn CPU, là có khả
năng cho một ứng dụng vẫn
đáp ứng được nhu cầu đầu vào. Trong một chương trình
thread đơn, nếu khối thread tính toán chính trên một nhiệm vụ lớn, toàn bộ ứng dụng
có thể xuất hiện để đóng băng. Bằng cách di chuyển nhiệm vụ lớn này tới một
thread worker để chạy đồng thời với thread tính toán chính, nó có thể cho các ứng
dụng đáp ứng đầu vào của người dùng trong khi vẫn thực hiện nhiệm vụ tính toán.
Hệ điều hành sắp xếp các thread theo một trong hai cách sau:
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 9 Lớp : K51CHTTT
- Chế độ lập lịch ưu tiên(preemptive) thường được coi là phương pháp tốt hơn,
vì nó cho phép hệ điều hành xác định khi nào một chuyển trạng thái xảy ra. Sự
bất lợi của phương pháp này là hệ điều hành có thể làm cho bối cảnh chuyển
đổi tại một thời gian không thích hợp, gây đảo ngược ưu tiên hoặc các hiệu
ứng tiêu cực nào khác mà có thể tránh bằng cooperative multithreading.
- Cooperative multithreading, mặt khác,dựa vào bả
n thân các thread để từ bỏ
kiểm soát sau khi tới một điểm dừng. Điều này có thể tạo ra vấn đề nếu một
thread đang đợi cho tới khi một tài nguyên trở nên sẵn sàng.
Phần cứng máy tính truyền thống không có nhiều hỗ trợ cho multi thread, vì
chuyển đổi giữa các thread nói chung đã nhanh hơn chuyển đổi giữa các tiến trình.
Bộ xử lý trong hệ thống nhúng, có nhiều yêu cầu cao hơn đói vớ
i xử lý thời gian
thực, có thể hỗ trợ multi thread bằng cách giảm thời gian chuyển đổi giữa các
thread, có lẽ bằng cách cấp phát một tập tin đăng ký chuyên biệt dành riêng cho mỗi
thread thay vì lưu trữ hoặc khôi phục lại một tập tin đăng ký phổ biến. Vào cuối
nhữn năm 90, ý tưởng thực hiện các tính toán đồng thời đã được biết đến như là
multi thread đồng thời. Tính năng này đã đượ
c giới thiệu trong bộ vi xử lý Pentium
4 của Intel, có tên là Hyper Threading.
1.4. Tiến trình, thread nhân, thread người dùng, fiber
Một tiến trình là đơn vị nặng nhất của lập lịch nhân. Tiến trình sở hữu tài
nguyên được cấp phát bởi hệ điều hành. Tài nguyên bao gồm bộ nhớ, xử lý tập tin,
socket, thiết bị xử lý, và window. Tiến trình không chia sẻ không gian địa chỉ hoặc
tài nguyên tập tin ngoại trừ thông qua phương pháp rõ ràng như thừa kế tập tin x
ử lý
hoặc các phân đoạn chia sẻ bộ nhớ, hoặc lập bản đồ cùng một tập tin tron một cách
đã được chia sẻ. Tiến trình thường là đa nhiệm phòng ngừa (preemptively
multitasked).
Một thread nhân là thành phần nhẹ nhất của bộ nhân lập lịch. Ít nhất một
thread nhân tồn tại trong một tiến trình. Nếu nhiều thread nhân tồn trại trong một
tiến trình, sau đó chúng chia sẻ bộ nhớ và tài nguyên tập tin. Thread nhân là đa
nhiệm phòng ngừa (preemptively multitasked) nếu quá trình lập lịch của hệ điều
hành là “phòng ngừa”. Thread nhân không sử hữu tài nguyên cho riêng nó ngoại trừ
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 10 Lớp : K51CHTTT
một ngăn xếp, một bản sao đăng ký bao gồm bộ đếm chương trình, và lưu trữ thread
địa phương (thread-local). Hạt nhân có thể chỉ định một thread cho mỗi lõi (core)
trong một hệ thống (bởi vì mỗi core tự chia nó thành nhiều core logic nếu nó hỗ trợ
multi thread, hoặc chỉ hỗ trợ một core logic với mỗi core vật lý nếu không hỗ trợ
multi thread) và có thể đổi chỗ thread để được chặn. Tuy nhiên, thread nhân mất
nhi
ều thời gian hơn thread người dùng khi đổi chỗ.
Thread đôi khi được thể hiện trong thư viện không gian người dùng
(userspace), do đó được gọi là user thread. Kernel không phải để ý tới chúng, chúng
được quản lý và lập lịch bởi userspace. User thread như được thực hiện bởi máy ảo
nên cũng được gọi là green thread. User thread thường tạo ra và quản lý nhanh
chóng, nhưng không thể tận dụng lợi thế của multi thread hay đa xử lý và bị chặn
nếu t
ất cả các thread nhân liên quan bị chặn ngay cả khi có một số user thread đã
sẵn sàng để chạy.
Fibers là thành phần thậm chí còn nhẹ hơn cả đơn vị của bộ lập lịch dự kiến
hợp tác: một fiber đang chạy phải rõ ràng “năng suất” để cho phép những fiber khác
chạy, mà làm cho sự thể hiện của chúng dễ dàng hơn nhiều so với thread nhân hay
thread người dùng. Một fiber có thể được lập lịch
để chạy trong bất kỳ thread nào
trong cùng một tiến trình. Điều này cho phép những ứng dụng đạt được những hiệu
suất cải tiến bằng cách quản lý sự lập lịch, thay vì dựa vào bộ lập lịch của nhân (mà
có thể không được điều chỉnh cho ứng dụng). Môi trường lập trình song song như
OpenMP thường thể hiện nhiệm vụ của chúng thông qua fiber.
1.5. Vấn đề đư
a ra của thread và fiber
1.5.1. Truy cập đồng thời và cấu trúc dữ liệu
Những thread trong cùng một tiến trình chia sẻ cùng không gian địa chỉ. Điều
này cho phép đồng thời chạy các đoạn mã thành từng cặp để trao đổi dữ liệu một
cách thuận tiện và chặt chẽ mà không cần chi phí hoạt động hoặc sự phức tạo của
giao tiếp liên tiến trình (inter-process communication - IPC). Tuy nhiên, khi chia sẽ
giữa các thread, kể cả nhữ
ng cấu trúc dữ liệu đơn giản cũng trở nên rủi ro nếu chúng
yêu cầu nhiều hơn một cấu trúc CPU để cập nhật: hai thread có thể cố gắng cập nhật
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 11 Lớp : K51CHTTT
cấu trúc dữ liệu cùng lúc và tìm thấy những sự thay đổi không mong muốn. Lỗi xảy
ra bởi những rủi ro có thể rất khó để tái sản xuất và cô lập.
Để ngăn ngừa điều này, những API threading cung cấp những API để đồng bộ
hóa như mutexes để khóa cấu trúc dữ liệu chống lại quyền truy cập đồng thời. Trên
hệ thống một bộ vi xử lý, một thread đang chạ
y trong một mutex đã bị khóa phải
tạm dừng và gây ra một ngữ cảnh chuyển. Trên hệ thống đa xử lý, thread có thể
thay bởi việc dò ý kiến các mutex trong một spinlock.
1.5.2. Vào/ ra và bộ lập lịch
Sự thể hiện của thread người dùng hoặc fiber thường hoàn toàn bằng
userspace. Như là một kết quả, ngữ cảnh chuyển giữa thread người dùng và fiber
trong cùng một tiến trình cực kỳ hiệu quả bởi vì nó không yêu c
ầu bất kỳ sự tương
tác nào với nhân trong tất cả : một ngữ cảnh chuyển có thể được thực hiện tại “địa
phương” do đó tiết kiệm các CPU đăng ký sử dụng bởi thread hoặc fiber thực thi
người dùng và sau đó tải những đăng ký bắt buộc bởi thread người dùng hoặc fiber
được thực thi. Kể từ khi sự lập lịch xảy ra ở không gian người dùng, chính sách lập
lịch sẽ dễ dàng phù hợp với yêu cầu của khối lượng công việc của chương trình hơn.
Tuy nhiên, cách sử dụng các khối lời gọi hệ thống trong thread người dùng
hoặc fiber có thể khó giải quyết. Nếu một thread người dùng hoặc một fiber thực
hiện một lời gọi hệ thống, những thread người dùng hoặc fiber khác trong tiến trình
sẽ không thể chạy cho tới khi có lời gọi trả v
ề của hệ thống. Một ví dụ điển hình của
vấn đề này khi thực hiện đầu vào/ đầu ra : hầu hết các chương trình được viết để
thực hiện việc đồng bộ đầu vào/đầu ra. Khi một toán tử đầu vào/đầu ra được khởi
tạo, một lời gọi hệ thống sẽ được thực hiện và không trả về cho tới khi toán tử đầu
vào/đầu ra được hoàn thành. Trong giai đoạn can thiệp, toàn bộ tiến trình sẽ bị chặn
bởi nhân và không thể chạy mà bỏ mặc các thread và fiber khác trong cùng tiến
trình.
Một giải pháp phổ biến cho vấn đề này là cung cấp một API I/O để thể hiện
giao diện đồng bộ bằng cách sử dụng đầu vào/ đầu ra không bị chặn bên trong, và
lập lịch cho những thread người dùng hoặc fiber khác trong khi toán tử đầu vào/ đầu
ra đang được x
ử lý. Những giải pháp tương tự có thể được cung cấp cho những lời
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 12 Lớp : K51CHTTT
gọi khác. Như một sự lựa chọn, chương trình có thể được viết để tránh việc sử dụng
của việc đồng bộ hóa đầu vào/ đầu ra hoặc khối lời gọi hệ thống khác.
Việc sử dụng các thread nhân để đơn giản hóa mã người dùng bằng cách di
chuyển một số khía cạnh phức tạp nhất của thread vào trong nhân. Chương trình
không cần phải lập lịch cho thread. Mã người dùng có th
ể được viết bằng 1 phong
cách thủ tục quen thuộc, bao gồm lời gọi tới một khối APIs mà khong đòi các thread
khác. Tuy nhiên thread nhân trong hệ thống đơn xử lý có thể đòi hỏi một ngữ cảnh
chuyển giữa các thread trong bất kỳ thời điểm nào, vào do đó tiếp xúc với mối rủi ro
và lỗi truy cập đồng thời.
1.6. Các mô hình
1.6.1. Mô hình 1:1 (thread cấp nhân)
Thread được tạo ra bởi người dùng t
ương ứng 1:1 với các thực thể có thể lập
lịch trong nhân. Đây là việc thực hiện đơn giản nhất có thể trong thread. Trên Linux,
thư viện ngôn ngữ C thông thường thực hiện phương pháp này (bằng các thư viện
POSIX Pthread NPTL hoặc các LinuxThread cũ hơn). Phương pháp tương tự cũng
được sử dụng bởi Solaris, NetBSD và FreeBSD.
1.6.2. Mô hình N:1 (thread cấp người dùng)
Một mô hình N:1 ngụ rằng tất cả các thread cấp ứng dụng
đều vạch ra một đơn
thực thể được lập lịch cấp nhân. Nhân không có tri thức về thread ứng dụng. Với
phương pháp này, ngữ cảnh chuyển có thể được hoàn thành rất nhanh và ngoài ra,
nó có thể được thực hiện ngay cả trên những nhân đơn giản mà không hỗ trợ thread.
Một trong những nhược điểm hiển nhiên là nó không thể tăng tốc phần cần trên bộ
xử lý multi thread hay nhiều bộ x
ử lý máy tính: không bao giờ có nhiều hơn một
thread được lập lịch cùng thời điểm. Nó được sử dụng bởi GNU Portable Threads.
1.6.3. Mô hình N:M (thread tích hợp)
Mô hình N:M chỉ ra với N thread ứng dụng tương ứng với M thực thể nhân
Đây là kết hợp giữa cấp nhân(1:1) và cấp người dùng(N:1). Nói chung, hệ thống
thread N:M thể hiện phức tạp hơn thread nhân hoặc thread người dùng, bởi vì đòi
hỏi mã của cả thread nhân và thread người dùng. Trong th
ể hiện N:M, thư viện
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 13 Lớp : K51CHTTT
thread đáp ứng cho bộ lập lịch thread người dùng trên nhưng thực thể có thể lập lịch
được luôn sẵn sàng. Điều này làm cho ngữ cảnh chuyển giữa các thread rất nhanh,
và nó tránh được những lời gọi hệ thống. Tuy nhiên, điều này tăng độ phức tạp và
khả năng đảo ngược độ ưu tiên, cũng như lập trình gần tối ưu mà không cần nhiều
sự phố
i hợp giữa các bộ lập lịch vùng người dùng và vùng nhân.
1.7. Ngôn ngữ hỗ trợ
Rất nhiều ngôn ngữ lập trình hỗ trợ thread trong một số khả năng nào đó.
Nhiều triển khai của C và C++ không cung cấp hỗ trợ trực tiếp cho thread ngay của
riêng mình, nhưng cung cấp truy cập vào các thread API cung cấp bởi hệ điều hành.
Một vài ngôn ngữ bậc cao như Java, Python và .Net,tiếp xúc với thread để phát
triển. Một s
ố ngôn ngữ lập trình khác cũng cố gắng tóm tắt khái niệm về sự đồng
thời và thread từ nhà phát triển.
Trong chương tiếp theo sẽ đi sâu vào lập trình thread trong hệ thống Unix với
POSIX Thread.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 14 Lớp : K51CHTTT
CHƯƠNG 2: POSIX THREAD PROGRAMMING
2.1. Tổng quan về Pthread
2.1.1. Khái niệm Pthread
Trong lịch sử, các nhà phát triển phần cứng đã triển khai thực hiện việc sử độc
quyển sở hữu về các phiên bản của thread. Các thực hiện này khác biệt đáng kể so
với các thực hiện khác làm cho các nhà lập trình khó khăn trong việc phát triển các
ứng dụng thread di động. Để tận dụng đầy đủ các khả năng được cung cấp bởi
thread, một giao diện lập trình chuẩn hóa đã được yêu cầu:
Với hệ thống UNIX, giao diện này được xác định bởi chuẩn IEEE
POSIX 1003.1c
Những thực hiện tôn trọng những tiêu chuẩn này được gọi là POSIX thread,
hoặc Pthreads.
Các tiêu chuẩn của POSIX đã tiếp tục tiến triển và trải qua các phiên bản, bao
gồm cả các đặc điểm kỹ thuật của Pthread. Phiên bản mới nhất được bi
ết là IEEE
1003.1, phát hành 2004.
2.1.2. Tại sao lại sử dụng Pthread?
Động lực chính đế sử dụng Pthread là để nhận ra hiệu quả tiềm năng của một
chương trình. Khi so sánh với việc tạo ra và quản lý một process, một thread được
tạo ra với ít chi phí hoạt động của hệ điều hành hơn. Quản lý thread cũng yêu cầu ít
tài nguyên hệ thống hơn là quản lý process. Ví dụ: bảng sau so sánh kết quả thời
gian giữa thử tục fork() và thủ tục pthreads_create(). Thời gian được tính toán với
việc tạo ra 50000 process/thread, được thực hiện với tiện ích time, tính theo đơn vị
là giây và không có đánh dấu tối ưu hóa.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 15 Lớp : K51CHTTT
Tất cả các thread trong một process đều chia sẻ cùng một không gian địa chỉ.
Những giao tiếp inter-thread hiệu quả hơn và trong nhiều trường hợp, dễ sử dụng
hơn giao tiếp inter-process. Những ứng dụng threaded cung cấp hiệu suất tiềm năng
và lợi thế hơn các ứng dụng không sử dụng thread trong một số trường hợp như :
- Sự chồng CPU làm việc với đầu vào/đầ
u ra : ví dụ, một chương chình có thể có
nhiều phần mà chúng thực hiện một tính toán đầu vào/ đầu ra dài. Trong khi
một thread đợi cho một hệ thống đầu vào/ đầu ra được gọi để hoàn thành, CPU
làm việc chuyên sâu có thể được thực hiện bởi thread khác.
- Lập lịch ưu tiên/thời gian thực: những nhiệm vụ mà quan trọng hơn có thể
được lập lịch để thay thế hoặc ngắt các nhiệm vụ có
độ ưu tiên ít hơn.
- Quản lý sự kiện không đồng bộ: những nhiệm vụ mà phục vụ các sự kiện với tần số
vô định hoặc kéo dài có thể được xen kẽ. Ví dụ, một web server có thể đồng thời
chuyển dữ liệu từ các yêu cầu trước và quản lý sự xuất hiện của các yêu cầu mới.
Động cơ chính để thúc đẩy việc cân nhắc sử d
ụng Pthread trên một kiến trúc
SMP ( symmetric multyprocessing ) để đạt được hiệu suất tối ưu.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 16 Lớp : K51CHTTT
2.1.3. Pthread API
Những Ptrhead API thông thường được định nghĩa trong chuẩn ANSI/IEEE
POSIX 1003.1 – 1995. Chuẩn POSIX tiếp tục được phát triển và trải qua các phiên
bản, bao gồm các đặc điểm kỹ thuật Pthreads. Phiên bản mới nhất được gọi là IEEE
Std 1003.1, 2004. Các thủ tục con trong đó bao gồm các API Pthreads có thể được
chính thức nhóm lại thành bốn nhóm chính:
Quản lý thread: các thủ tục làm việc trực tiếp với thread: tạo thread, tách, nối
… Chúng cũng bao gồ
m các hàm để thiết lập hoặc truy vấn thuộc tính thread
(như nối, lập lịch…).
Mutexes: các thủ tục xử lý việc đồng bộ hóa, được gọi là một “mutex”, viết tắt
của “mutual exclusion”. Các hàm mutex cung cấp các hàm tạo, phá hủy, khóa
và mở khóa mutexes. Các hàm này được bổ sung thêm các hàm thuộc tính
mutex để thiết lập hoặc sửa đổi các thuộc tính liên kết với mutexes.
Biến điều kiện: các thủ tục để
đánh địa chỉ liên kết giữa các thread để chia sẻ
một mutex. Tùy thuộc và các lập trình viên và những điều kiện cụ thể. Nhóm
này bao gồm các hàm để khởi tạo, phá hủy, đợi và signal dựa vào giá trị cụ thể
của biến. Các hàm để thiết lập/truy vấn thuộc tính biến điều kiện cũng được
bao gồm.
Đồng bộ hóa: các thủ tục quả lý đọc/ghi các khóa và các rào cản.
Quy
ước đặt tên: tất cả các định danh trong thư viện đều được bắt đầu với
pthread_. Một số ví dụ:
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 17 Lớp : K51CHTTT
Khái niệm về các đối tượng không rõ ràng trong thiết kế của API. Việc gọi cơ
bản để tạo ra hoặc sửa đổi các đối tượng không rõ ràng - các đối tượng không rõ
ràng có thể được sửa đổi bởi lệnh gọi các hàm thuộc tính để xử lý các thuộc tính
không rõ ràng. Pthread API chứa khoảng 100 thủ tục con. Hướng dẫn này sẽ tập
trung vào một tập hợp con của các thủ tục này, cụ thể, là nhữ
ng thủ tục mà có nhiều
khả năng có thể có hữu ích ngay lập tức với những người mới lập trình về Phtread.
Đối với tính di động, file pthread.h được include trong mỗi file mã nguồn sử dụng
thư viện Pthreads. Chuẩn POSIX hiện hành chỉ được định nghĩa trong ngôn ngữ C.
2.1.4. Biên dịch chương trình Threaded
Một vài ví dụ về lệnh biên dịch sử dụng cho code Pthread được liệt kê ở bảng
sau:
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 18 Lớp : K51CHTTT
2.2. Quản lý Thread
2.2.1. Các thủ tục chính
pthread_create(thread, attr, start_routine, arg)
pthread_exit(status)
pthread_attr_itit(attr)
pthread_attr_destroy(attr)
2.2.2. Tạo Thread
Ban đầu, chương trình main() bao gồm một thread đơn mặc định. Tất cả những
thread khác phải được tạo ra từ lập trình viên. Hàm pthread_create tạo ra 1 thread
mới và làm chúng được thực thi. Thủ tục này được gọi số lần tùy ý và từ bất kỳ đâu
trong code của bạn. Các tham số trong thủ tụ
c pthread_create:
Thread: một định danh duy nhất cho một thread mới và được trả về bởi thủ tục
con.
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 19 Lớp : K51CHTTT
Attr: một đối tượng thuộc tính có thể được sử dụng để thiết lập các thuộc tính
cho thread. Bạn có thể xác định một đối tượng thuộc tính thread, hoặc để
NULL với giá trị mặc định.
Start_routine: thủ tục C để thread sẽ thực thi một lần khi nó được tạo ra.
Arg: một tham số đơn để có thể được truyền cho start_routine. Nó phải được
truyền bở
i tham chiếu như là con trỏ kiểu void.
Mỗi một lần được tao ra, thread có thể tạo ra các thread khác. Không có hệ
thống cập bậc hoặc phụ thuộc giữa các thread.
2.2.3. Thiết lập các thuộc tính cho Thread
Mặc định, một thread được tạo ra với một thuộc tính cố định. Một vài trong số
những thuộc tính có thể được thay đổi bởi người lập trình thông qua đối tượng thuộc
tính thread. Pthread_attr_init và pthread_attr_destroy được sử dụng để khởi tạo hoặc
phá hủy đối tượng thuộc tính thread. Những thủ tục khác được sử dụng sau đó để
truy vấ
n hoặc thiết lập những thuộc tính xác định trong đối tượng thuộc tính thread.
2.2.4. Hủy thread
Có một vài cách mà trong đó Pthread bị hủy:
Những thread mà trả về từ thủ tục bắt đầu của nó ( thủ tục chính với thread ban
đầu).
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 20 Lớp : K51CHTTT
Những thread thực hiện lời gọi tới hàm pthread_exit.
Những thread bị hủy từ những thủ tục khác bằng hàm pthread_cancel.
Tiến trình cuối bị ngắt trong khi thực hiện lời gọi tới hàm khác hoặc thoát khỏi
thủ tục con.
Hàm pthread_exit được sử dụng để thoát khỏi một thread. Thông thường, thủ
tục pthread_exit() được gọi sau khi 1 thread đã hoàn thành công việc của nó và
không còn yêu cầu nào. Nếu hàm main() kết thúc tr
ước khi những thread được tạo ra
và thoát với hàm pthread_exit(), những thread khác vẫn tiếp tục được thực hiện.
Ngoài ra, chúng cũng sẽ tự động bị ngắt khi hàm main() kết thúc. Người lập trình có
thể tùy chọn chỉ định một trạng thái (status) ngắt, được lưu như một con trỏ kiểu
void cho bất kỳ thread nào để có thể tham gia vào lời gọi thread. Tóm lại: thủ tục
pthead_exit() không phải để đóng file, bất kỳ file nào
được mở bên trong thread, các
file này sẽ vẫn mở sau khi thread bị ngắt.
Ví dụ về tạo thread và ngắt thread: ví dụ này tạo ra 5 thread với thủ tục
pthread_create(). Mỗi thread sẽ in ra thông điệp “Hello world” và sau đó được ngắt
bới lời gọi hàm pthread_exit():
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
long tid;
tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 21 Lớp : K51CHTTT
int rc;
long t;
for(t=0; t<NUM_THREADS; t++){
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
2.2.5. Truyền tham số cho Thread
Thủ tục pthread_create() cho phép người lập trình có thể truyền một đối số cho
thủ tục khởi tạo thread. Trong trường hợp có nhiều tham số cần truyền, sự giới hạn
này rất dễ dàng được vượt qua bằng cách tạo ra 1 cấu trúc struct bao gồm tất cả các
tham số, sau đó truyền 1 tham số con trỏ của struct này vào thủ t
ục
pthread_create(). Tất cả các tham số đều phải được truyền bằng tham chiếu và kiểu
(void *). Ví dụ về cách truyền tham số:
Ví dụ 1: truyền 1 tham số:
long *taskids[NUM_THREADS];
for(t=0; t<NUM_THREADS; t++)
{
taskids[t] = (long *) malloc(sizeof(long));
*taskids[t] = t;
printf("Creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,(void *) taskids[t]);
...
}
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 22 Lớp : K51CHTTT
Ví dụ 2: Truyền nhiều tham số sử dụng struct.
struct thread_data{
int thread_id;
int sum;
char *message;
};
struct thread_data thread_data_array[NUM_THREADS];
void *PrintHello(void *threadarg)
{
struct thread_data *my_data;
...
my_data = (struct thread_data *) threadarg;
taskid = my_data->thread_id;
sum = my_data->sum;
hello_msg = my_data->message;
...
}
int main (int argc, char *argv[])
{
...
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
thread_data_array[t].message = messages[t];
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) &thread_data_array[t]);
...
}
2.2.6. Nối và tách Thread
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 23 Lớp : K51CHTTT
2.2.6.1. Những thủ tục chính
Pthread_join (threadid,status)
Pthread_detach(threadid,status)
Pthread_attr_setdetachstate(attr,detachstate)
Pthread_attr_getdetachstate(attr,detachstate)
2.2.6.2. Nối Thread
Nối (joining) là 1 cách để thực hiện việc đồng bộ hóa giữa các thread. Ví dụ:
Thủ tục con pthread_join() khóa lời gọi thread cho tới khi threadid xác định bị
ngắt. Người lập trình cũng có thể nhận được trạng thái ngắt trả về của thread mục
tiêu nếu nó được xác định từ lời gọi pthread_exit(). Một thread đang được nối có thể
gặp lời gọi pthead_join(). Nó là lỗi logic khi cố thử nhiều phép nối trên cùng một
thread. Hai phương thức đồng bộ hóa khác là mutexes và biến điều kiệ
n sẽ được
thảo luận ở phần sau.
2.2.6.3. Có thể nối được hay không?
Khi một thread được tạo ra, một trong những thuộc tính của nó định nghĩa khi
nào thì có thể nối được hoặc tách được. Chỉ thread mà được tạo ra với thuộc tính là
có nối được thì mới có thể được nối. Nếu một thread được tạo ra với thuộc tính là
tách, nó không bao giờ được nối. Bản nháp cuối cùng của chu
ẩn POSIX xác định
rằng các thread nên được tạo ra với thuộc tính có thể nối được, để rõ ràng khi tạo ra
một thread có thể nối hoặc tách, tham số attr trong thủ tục pthread_create() được sử
dụng. Quá trình có 4 bước là:
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 24 Lớp : K51CHTTT
Khai báo một biến thuộc tính của pthread với kiểu dữ liệu là pthread_attr_t
Khởi tạo biến thuộc tính với hàm pthread_attr_init()
Thiết lập thuộc tính tách được với hàm pthread_attr_setdetachstate()
Khi đã hoàn thành, giải phóng tài nguyên được sử dụng bởi các thuộc tính với
hàm pthread_attr_destroy().
2.2.6.4. Tách (detaching)
Thủ tục pthread_detach() có thể được sử dụng để tách một thread thậm chí
thread đó được tạo ra với thuộc tính là có thể nố
i được.Không có thủ tục ngược lại.
Ví dụ về nối thread:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 4
void *BusyWork(void *t)
{
int i;
long tid;
double result=0.0;
tid = (long)t;
printf("Thread %ld starting...\n",tid);
for (i=0; i<1000000; i++)
{
result = result + sin(i) * tan(i);
}
printf("Thread %ld done. Result = %e\n",tid, result);
pthread_exit((void*) t);
}
int main (int argc, char *argv[])
{
Khóa luận tốt nghiệp Nghiên cứu lập trình thread và ứng dụng
Sinh viên: Cấn Việt Dũng 25 Lớp : K51CHTTT
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc;
long t;
void *status;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(t=0; t<NUM_THREADS; t++) {
printf("Main: creating thread %ld\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, (void *)t);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
/* Free attribute and wait for the other threads */
pthread_attr_destroy(&attr);
for(t=0; t<NUM_THREADS; t++) {
rc = pthread_join(thread[t], &status);
if (rc) {
printf("ERROR; return code from pthread_join() is %d\n", rc);
exit(-1);
}
printf("Main: completed join with thread %ld having a status of %ld\n",t,(long)status);
}
printf("Main: program completed. Exiting.\n");
pthread_exit(NULL);
}