• Like
  • Save
Linux+04
Upcoming SlideShare
Loading in...5
×
 

Linux+04

on

  • 3,609 views

Bai giang Linux (Phan4)

Bai giang Linux (Phan4)

Statistics

Views

Total Views
3,609
Views on SlideShare
3,607
Embed Views
2

Actions

Likes
0
Downloads
247
Comments
1

1 Embed 2

http://www.searchqu.com 2

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Linux+04 Linux+04 Presentation Transcript

    • .: CÔNG NGHỆ LINUX :. Môn học: KHÓA 3 CHƯƠNG TRÌNH ĐÀO TẠO THẠC SĨ CNTT QUA MẠNG Giảng viên: TS. Tô Tuấn (Viện CNTT, BQP) Email: totuan4@yahoo.com Trợ lý kỹ thuật: Nguyễn Vạn Phúc, Vũ Mạnh Cường ĐẠI HỌC QUỐC GIA TP. HỒ CHÍ MINH TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN
    • Chương 7: Xử lý tiến trình trong Linux
      • Bao gồm các phần sau:
        • Khái quát về tiến trình trong Linux
        • Cách hoạt động của tiến trình
        • Cấu trúc tiến trình
        • Tạo lập tiến trình
        • Đọc thông tin về các tiến trình
      CITD - VNUHCM
      • 7.1. Khái quát về tiến trình trong Linux
      • Mục tiêu: Nắm vững cơ chế hoạt động của các HĐH đa nhiệm thông qua khái niệm tiến trình ( Process )
      • Điểm nổi bật của các HĐH tựa Unix là khả năng chạy đồng thời nhiều chương trình (Multi-Programming).
      • HĐH xem mỗi đơn thể mã lệnh mà nó điều khiển là Tiến trình .
      • Một ứng dụng có thể bao gồm nhiều tiến trình kết hợp với nhau.
      • - Các tiến trình cùng hoạt động  chia sẻ thời gian CPU, có thể dùng chung vùng nhớ và các tài nguyên hệ thống khác.
      • Các lệnh của Linux thực tế là những tiến trình riêng lẻ, có khả năng kết hợp và truyền dữ liệu cho nhau thông qua cơ chế giao tiếp liên tiến trình IPC (Inter-Process Communications)
      • Chúng ta sẽ biết cách tạo, hủy, tạm dừng tiến trình trong Linux. Đồng thời, tìm hiểu sâu cơ chế đồng bộ hóa và giao tiếp giữa các tiến trình.
      • - Không như môi trường đơn nhiệm chỉ có một tiến trình hoạt động, môi trường đa nhiệm có tài nguyên rất hạn hẹp.
      • Khi hệ thống yêu cầu, tiến trình phải trong trạng thái sẵn sàng nhường quyền xử lý CPU cho các tiến trình khác ở bất kỳ thời điểm nào.
      • Tiến trình được định nghĩa: Là một thực thể điều khiển đoạn mã lệnh có riêng một không gian địa chỉ, có ngăn xếp riêng, có bảng chứa các mô tả tập tin đang mở và đặc biệt là có một định danh PID ( Process Identifier ) duy nhất trong toàn bộ hệ thống vào thời điểm tiến trình đang chạy.
      • - Nhiều tiến trình có thể thực thi trên cùng một máy với cùng một HĐH, cùng một người dùng (hoặc nhiều người dùng đăng nhập khác nhau). Ví dụ: Shell Bash là một tiến trình có thể thực thi lệnh ls hay cp (trong khi bản thân hai lệnh trên là những tiến trình có thể hoạt động tách biệt khác)
      • Hai tiến trình khác nhau không thể xâm phạm vùng nhớ của nhau  Tuy nhiên, để chia sẻ dữ liệu giữa hai tiến trình có thể sử dụng vùng không gian địa chỉ chung.
      • 7.2. Cách hoạt động của tiến trình
      • Trong môi trường HĐH Linux, một tiến trình có các trạng thái sau:
        •  Running (Đang chạy): Tiến trình chiếm quyền xử lý CPU
        •  Waiting (Chờ): Tiến trình bị HĐH “tước” quyền xử lý CPU
        •  Suspend (Tạm dừng): HĐH tạm dừng tiến trình  Sleeping
      • Tại dòng lệnh, có thể bấm Ctrl-Z để tạm ngừng tiến trình sau đó dùng lệnh bg để đưa nó vào hậu trường để sau đó dùng fg chuyển lên mặt trước.
      • Hàm fork() để nhân bản một tiến trình.
      • Hàm system() để tạo lập tiến trình mới.
      • Hàm exec() dùng thay thế tiến trình hiện hành.
      • 7.3. Cấu trúc tiến trình
      • Cách thức quản lý tiến trình của Linux:
        •  Hai user neil và rick cùng đăng nhập và chạy chương trình grep đồng thời  HĐH lúc này sẽ quản lý và nạp mã truy cập chương trình grep vào hai vùng nhớ khác nhau và gọi mỗi phân vùng như là một tiến trình .
        • Hình 7.1 người dùng neil chạy
        • chương trình grep tìm chuỗi “abc”
        • trong tập tin treck.txt còn người
        • dùng rick chạy chương trình grep
        • tìm chuỗi “cde” trong tập tin
        • somefile.doc
      Hình 7.1 Tiến trình quản lý bởi HĐH neil $grep abc treck.txt PID 101 Code Data S=abc Library filedes Mã lệnh grep Thư viện C rick $grep cde somefile.doc PID 102 Code Data S=cde Library filedes treck.txt somefile.doc Không gian của Hệ điều hành
      • Dùng lệnh ps cho phép xem thông tin về các tiến trình mà Linux đang kiểm soát (có thể sử dụng thêm tham số “ -af” để liệt kê chi tiết thông tin về các tiến trình):
      Hình 7.1.a Liệt kê thông tin về các tiến trình trên hệ thống
      • Mỗi tiến trình được gán cho một định danh gọi là PID (Process Identifier)
      • PID là một số nguyên dương có giá trị từ 2 – 32768
      • Tiến trình init được gọi và chạy ngay khi ta khởi động HĐH. Đây là tiến trình quản lý và tạo ra mọi tiến trình con khác  Có PID là 1
      • Cách thức gán PID : Khi một tiến trình mới yêu cầu khởi động, HĐH sẽ chọn lấy một giá trị số nguyên trong khoảng 2 – 32768 (số nguyên này chưa bị tiến trình đang chạy nào chiếm giữ) và cấp phát cho tiến trình này. Khi tiến trình chấm dứt, HĐH sẽ thu lại số PID để cấp phát cho tiến trình khác trong lần sau.
      • Trên Unix/Linux, thư mục /proc chứa các file lưu trữ thông tin về những tiến trình đang chạy.
      • Theo quy ước, mỗi tiến trình hoạt động trong không gian địa chỉ ảo độc lập do hệ thống cấp phát  4GB (có liên quan đến phân vùng SWAP đã được đề cập ở Chương 1)
      • 7.3.1. Bảng thông tin tiến trình
      • HĐH lưu trữ một cấu trúc danh sách bên trong hệ thống gọi là bảng tiến trình (Process Table).
      • Bảng tiến trình quản lý tất cả các PID của hệ thống cùng với thông tin chi tiết về các tiến trình đang chạy.
      • Bảng tiến trình tựa như bảng của CSDL lưu các record là thông tin về tiến trình. Trong đó, số PID thường được sử dụng làm khóa chính và đánh chỉ mục để truy xuất thông tin.
      • Trong các phiên bản Unix/Linux cũ, giới hạn không được có hơn 256 tiến trình chạy đồng thời. Tuy nhiên, trên các hệ Unix/Linux có phiên bản mới hiện nay  số tiến trình là không giới hạn chỉ phụ thuộc vào bộ nhớ sẵn có.
      • 7.3.2. Xem thông tin của tiến trình
      • Sử dụng lệnh ps để hiển thị thông tin chi tiết về các tiến trình (Xem lại Hình 7.1.a)
      • Lệnh trên liệt kê nhiều tiến trình, kể cả những tiến trình chạy ở chế độ đồ họa X-Window .
      • Các thông tin về kết xuất của lệnh ps như sau:
        • • UID : Tên người dùng đã gọi tiến trình
        • • PID : Số định danh mà hệ thống cung cấp cho tiến trình
        • • PPID : Số định danh của tiến trình cha ( init có PID là 1 )
        • • STIME : Thời điểm bắt đầu đưa tiến trình vào ch ạy
        • • TIME : Khoảng thời gian chiếm dụng CPU của tiến trình
        • • CMD : Toàn bộ dòng lệnh triệu gọi ti ến trình
        • • TTY : Chỉ ra màn hình Terminal ảo nơi gọi thực thi tiến trình
      • 7.3.3. Các tiến trình hệ thống
      • Sử dụng lệnh ps với tham số là “ -ax ” để xem các tiến trình của hệ thống
      • Xét tiến trình ở dòng đầu: Các tiến trình hệ thống (ở đây là init ) không gắn với terminal nào hết nên ở cột TTY của tiến trình init  sẽ có giá trị là “ ? ”
      • Tiến trình cuối cùng khi mà init gọi trước khi chuyển giao quyền điều khiển cho Shell đó là tiến trình getty ( Trình yêu cầu nhập username / password lúc đăng nhập ).
      • 7.3.4. Điều phối tiến trình
      • Các HĐH đa nhiệm, đa tiến trình như Unix/Linux (hay cả MS Windows NT/2000/XP/2003,…) điều có trách nhiệm điều phối sự hoạt động đồng bộ của các tiến trình.
      • Bộ điều phối tiến trình ( Process Scheduler ) của HĐH liên tục xoay vòng và cung cấp một thời gian đủ để các tiến trình thực hiện một phần công việc của nó. Thông thường, khoảng thời gian cho phép tiến trình chiếm giữ CPU là rất nhỏ.
      • - Không một tiến trình nào “độc chiếm” toàn bộ CPU suốt thời gian dài gây ảnh hưởng đến các tiến trình khác.
      • Mỗi tiến trình có thể được cấp một độ ưu tiên ( Priority ) nhất định.
      • Độ ưu tiên của tiến trình có thể điều chỉnh được bằng lệnh renice
      • 7.4. Tạo lập tiến trình
      • 7.4.1. Gọi tiến trình mới bằng hàm system()
      • Gọi một chương trình khác bên trong một chương trình đang thực thi bằng hàm system()
      • #include <stdlib.h>
          • int system(const char *cmdstr)
      • Hàm này gọi lệnh chứa trong cmdstr và chờ lệnh chấm dứt mới quay về nơi gọi hàm, tương đương với việc gọi shell thực thi lệnh:
          • $ sh –c <cmdstr>
      • Hàm system() trả về:
        •  Mã lỗi 127 nếu không thể khởi động shell và thực thi lệnh được
        •  Mã lỗi –1 nếu gặp lỗi khác
        •  Ngược lại mã trả về sẽ là mã do < cmdstr> sau khi thực thi trả về
      • - Ví dụ 7-1 : system.c
      • 7.4.2. Thay thế tiến trình hiện hành với hàm exec()
      • Hàm exec() sẽ thay thế toàn bộ ảnh của tiến trình hiện tại {tiến trình A} (bao gồm mã lệnh, dữ liệu, bảng mô tả file) bằng ảnh của một tiến trình khác {tiến trình B}
      • Việc thay thế này chỉ giữ lại số PID của tiến trình A
      • Hàm thay thế ảnh của tiến trình bao gồm tập các hàm sau:
      • #include <unistd.h>
      • int execl(const char *path, const char *arg,...);
      • int execlp(const char *file,const char *arg,...);
      • int execle(const char *path, const char *arg,..., char *const envp[]);
      • int execv(const char *path, char *const argv[]);
      • int execvp(const char *file, char *const argv[]);
      • int execve(const char *file, char *const argv[], char *const envp[]);
      • - Ví dụ 7-2 : pexec.c
      • 7.4.3. Nhân bản tiến trình với hàm fork()
      • Việc thay thế tiến trình đôi khi cũng bất lợi do tiến trình mới tạo ra chiếm toàn bộ không gian của tiến trình cũ và không còn khả năng kiểm soát tiến trình cũ nữa.
      • Sử dụng hàm fork() để nhân bản tiến trình.
      • Hàm fork() sẽ quay về nơi gọi hai lần với hai giá trị trả về khác nhau.
      • Cách thức khai báo:
      • #include <sys/types.h>
      • #include <unistd.h>
      • pid_t fork()
      • Hình 7.2 minh họa cách nhân bản
      • tiến trình bằng fork()
      Hình 7.2 Cơ chế nhân bản tiến trình của fork() Khởi tạo tiến trình chính Gọi fork() Mã lệnh kế tiếp của tiến trình ban đầu (tiến trình cha) Mã lệnh thực thi tiến trình mới (tiến trình con) Trả về PID của tiến trình con Trả về trị 0
      • Đoạn mã điều khiển và sử dụng hàm fork() thường có dạng chuẩn sau:
      • pid_t new_pid;
      • new_pid=fork(); /*Nhân bản tiến trình*/
      • switch (new_pid) {
      • case –1:
      • printf(“ Can not fork new process ”);
      • break;
      • case 0:
      • printf(“This is child process”);
      • /*Mã lệnh dành cho tiến trình con*/
      • break;
      • default :
      • printf(“This is parent process”);
      • /* Mã lệnh dành cho tiến trình cha*/
      • break;
      • }
      • - Ví dụ 7-3 : fork_demo.c
      • 7.4.4. Kiểm soát và đợi tiến trình con
      • Hàm fork() nhân bản tiến trình hiện hành thành hai tiến trình cha và con hoạt động độc lập với nhau.
      • Đôi khi tiến trình cha phải đợi tiến trình con thực thi xong tác vụ thì mới được tiếp tục thực thi.
      • Giải quyết vấn đề này bằng hàm wait()
          • #include <sys/types.h>
          • #include <sys/wait.h>
          • pid_t wait(int *status)
          • pid_t waitpid(pid_t pid,int *status, int options )
      • Sử dụng một số macro được khai báo sẵn trong <sys/wait.h> để diễn dịch một số trạng thái mà hàm wait() sẽ trả về ( Tham khảo )
      • Ví dụ 7-4 : wait_child.c và Ví dụ 7-5 : wait_child2.c
      • 7.4.5. Đón xử lý tín hiệu khi tiến trình con kết thúc
      • Tiến trình cha có thể đón bắt tín hiệu SIGCHLD khi tiến trình con chấm dứt và gán cho nó một tác vụ thực thi nào đó. Bằng cách này, tiến trình cha có thể không cần phải gọi wait() để chờ tiến trình con kết thúc mới có thể thực thi được tác vụ tiếp theo.
      • Trong Ví dụ 7-6, hàm catch_child() dùng để xử lý tín hiệu (hay còn được gọi là bộ xử lý tín hiệu)
      • Để gán một hàm xử lý tín hiệu cho một tín hiệu cụ thể nào đó  gọi hàm hệ thống signal()
      • Ví dụ 7-6 : child_signal.c
      • 7.4.6. Thay thế hàm system()
      • Có thể viết lại hàm system() mà không cần phải triệu gọi lệnh thông qua shell của hệ thống.
      • Ví dụ 7-7 kết hợp sử dụng hàm fork() với hàm thay thế ảnh của tiến trình execlp()
      • Ví dụ 7-7 : system2.c
      • 7.4.7. Bỏ rơi tiến trình con
      • Là tình huống khi cả hai tiến trình cha và con cùng hoạt động độc lập nhưng tiến trình cha kết thúc trước tiến trình con và đến khi tiến trình con kết thúc thì sẽ không còn điểm trở về để liên hệ với tiến trình cha đã sinh ra nó trước đó  Tiến trình con ở trạng thái zombie (bỏ rơi)
      • Linux sẽ tự động gán PPID của tiến trình con (đang ở trạng thái zombie) về giá trị 1 ( tiến trình init )
      • Ví dụ 7-8 : zombie_child.c
      • 7.4.8. Chuyển hướng xuất nhập của tiến trình
      • Mỗi tiến trình có một bảng mô tả tập tin riêng để lưu trữ thông tin về các tập tin đang mở. Có thể tận dụng kỹ thuật này để xây dựng bộ lọc dữ liệu bằng chuyển hướng xuất nhập.
      • Xét ví dụ đọc dữ liệu từ luồng nhập chuẩn stdin sau đó chuyển đổi dữ liệu nhập được thành chữ hoa.
      • Ví dụ 7-9 : upper.c
      • Ví dụ 7-10 : dataupper.c
      • 7.5. Đọc thông tin v ề các tiến trình
      • Tất cả các thông tin mà lệnh ps trả về được lấy trong thư mục /proc
      • Khi một tiến trình mới được tạo thì hệ thống sẽ tạo một thư mục con bên trong thư mục /proc với tên là số PID của tiến trình và sẽ tự động loại bỏ thư mục này khỏi /proc khi tiến trình trên kết thúc:
            • /proc
            • 1  tiến trình init
            • 1062  tiến trình khác
            • 1123
            • 1234
            • . . .
      • Trong từng thư mục con sẽ thấy một số tập tin ghi lại trạng thái của tiến trình.
      • Có thể xây dựng một Process Manager dựa trên thông tin lấy từ thư mục /proc
    • - Thực hiện lệnh sau sẽ thấy thông tin về tiến trình init chi tiết
      • Chân thành cảm ơn
        • Các anh/chị đã tham dự bài giảng.
        • Bộ phận Multi-media đã hỗ trợ chúng tôi trong quá trình thuyết giảng.
      Kết thúc chương 7 Hẹn gặp lại buổi học kế
    • Chương 8: Lập trình đa luồng
      • Bao gồm các phần sau:
        • Khái niệm chung
        • Kiểm tra sự hỗ trợ luồng
        • Tạo lập và hủy luồng
        • Chờ luồng kết thúc
        • Đồng bộ hóa luồng bằng Mutex
        • Đồng bộ hóa luồng bằng Biến điều kiện
        • Đồng bộ hóa luồng bằng S emaphore
        • Đặt thuộc tính cho luồng
        • Hủy bỏ và chấm dứt luồng
      CITD - VNUHCM
      • 8.1. Khái niệm chung
      • Luồng ( Thread ) là một phần của tiến trình sở hữu riêng ngăn xếp (stack) và thực thi độc lập ngay trong mã lệnh của tiến trình.
      • Trong mỗi tiến trình  có thể tạo ra nhiều luồng hoạt động song song với nhau (như cách tiến trình hoạt động song song)
      • Ưu điểm của luồng là hoạt động trong cùng không gian địa chỉ của tiến trình.
      • - Tập hợp nhóm các luồng  chia sẻ chung vùng nhớ của một tiến trình và có thể sử dụng chung biến toàn cục, vùng nhớ heap, bảng mô tà file, … của tiến trình.
      • Việc sử dụng luồng trong tiến trình hơn hẳn cách lập trình tuần tự  nhiều thao tác xuất nhập (hoặc hiển thị) có thể được tách rời và phân cho luồng chạy độc lập khi thực thi. Ví dụ trong môi trường GUI, việc copy dữ liệu vào đĩa  chương trình sẽ được thiết kế một luồng đọc-ghi dữ liệu, còn luồng khác hiển thị đồng hồ cát hoặc Progress Bar.
      • Khuyết điểm của việc sử dụng luồng là khả năng ngưng hoạt động của một luồng có thể ảnh hưởng đến các luồng khác hoặc toàn bộ tiến trình đang hoạt động. Nguyên nhân là luồng dùng chung vùng nhớ và không gian địa chỉ của tiến trình
      • Nếu như một tiến trình có vấn đề, nó sẽ bị HĐH cô lập hoàn toàn mà không ảnh hưởng đến các tiến trình khác.
      • Tiến trình có thể chạy được trên nhiều máy khác nhau trong khi luồng chỉ thực hiện được trên một máy và trong một tiến trình.
      • Việc sử dụng luồng hay tiến trình là tùy thuộc vào người sử dụng
        •  Trong Unix/Linux:
        •  Tiến trình chiếm rất ít tài nguyên và có thể thay thế luồng
        •  Luồng giúp đơn giản hoá công việc lập trình
      • 8.2. Kiểm tra s ự hỗ trợ luồng
      • Các phiên bản nguyên thủy của Unix/Linux không hỗ trợ luồng  chuẩn POSIX cung cấp thư viện để lập trình đa luồng.
      • Đối với các phiên bản sau này, trong phần Kernel của hệ thống  chấp nhận sự hỗ trợ luồng của thư viện POSIX.
      • Sử dụng giá trị hằng _POSIX_VERSION để kiểm tra tính chắc chắn của thư viện và các hàm lập trình luồng POSIX trong hệ thống. Nếu hằng trên không được định nghĩa tức là không sử dụng được thư viện tạo luồng; còn ngược lại sẽ nhận được giá trị 199506L (hoặc lớn hơn) cho biết có sự hỗ trợ luồng của HĐH.
      • Ví dụ 8-1 : thread_available.c
      • - Cách thức biên dịch:
      #gcc filename.c –o filename -lpthread hoặc #gcc –D_POSIX_C_SOURCE=199506L filename.c –o filename
      • 8.3. Tạo lập và hủy luồng
      • Khi chương trình chính bắt đầu  chính là một luồng ( thread )
      • Luồng điều khiển hàm main() của chương trình được gọi là luồng chính. Các luồng khác do tiến trình tạo ra được gọi là luồng phụ.
      • Tiến trình có định danh là PID , còn luồng cũng có một số định danh được gọi là thread ID
      • Để tạo ra một luồng mới  gọi hàm pthread_create() . Cách khai báo hàm này như sau:
      • #include <pthread.h>
      • int pthread_create(pthread_t *thread, pthread_attr_t *attr,
              • void *(*start_routine)(void*),
              • void *argv);
      • Ví dụ 8-2 : thread_create.c
      • 8.4. Chờ luồng kết thúc
      • 8.4.1. Chờ luồng hoàn thành xong tác vụ
      • Dùng hàm pthread_join() để đợi một luồng kết thúc:
      • #include <pthread.h>
      • int pthread_join(pthread_t th, void *thread_return);
      • Ví dụ 8-3 : thread_wait.c
      • 8.4.2. Chờ đồng thời nhiều luồng
      • Trong ứng dụng Client/Server  Trình Server phải mở nhiều luồng để phục vụ các Client.
      • Việc kiểm soát và chờ nhiều luồng cũng sử dụng hàm pthread_join()
      • Hàm này không chờ riêng từng luồng mà tự động đưa các luồng vào danh sách chờ.
      • Ví dụ 8-4 : thread_multiwait.c
      • 8.5. Đồng bộ hóa lu ồng bằng Mutex
      • Cần phải đảm bảo các luồng không đụng độ khi truy xuất tài nguyên chung.
      • Mutex là một khái niệm được đưa ra để giải quyết tranh chấp và đồng bộ hóa các tiến trình ( các khái niệm tương ứng như tiến trình, luồng, lập trình đồng bộ với Mutex, semaphore, v.v… được xem là nền tảng của một HĐH đa nhiệm )
      • 8.5.1. Mutex là gì?
      • Là cơ chế mà thư viện hỗ trợ luồng cung cấp để giải quyết tranh chấp và đồng bộ hóa các tiến trình.
      • Mutex được xem như là một cờ hiệu với hai trạng thái.
      • Mutex phải đảm bảo được 3 nguyên tắc sau:
        • Đơn nguyên: HĐH hoặc thư viện kiểm soát luồng phải đảm bảo thao tác xử lý trên Mutex của mỗi luồng là độc quyền. Khi một luồng đang thực hiện khoá Mutex, không một luồng nào khác được ngắt ngang thao tác này.
        • Độc quyền: Khi Mutex đã bị khóa, không một luồng nào thực hiện khóa Mutex tiếp theo cho đến khi Mutex được tháo khóa (unlock) bởi luồng chiếm giữ Mutex trước đó.
        • Không ở trạng thái chờ-bận (busy-wait) chiếm tài nguyên CPU: Sử dụng vòng lặp while (hay loop ) để liên tục chờ một sự kiện nào đó (đang trong tình trạng busy-wait )  Chiếm thời gian xử lý của CPU. Hệ thống phải đảm bảo là nếu có một luồng A nào đó yêu cầu sử dụng Mutex đang bị khóa bởi một luồng khác B thì luồng A không ở trạng thái busy-wait mà phải được đưa vào trạng thái ngủ ( sleeping )  Trạng thái ngủ không sử dụng cơ chế busy-waiting và không chiếm tài nguyên CPU của hệ thống. Khi Mutex được giải phóng bởi luồng B  hệ thống sẽ đánh thức ( wake-up ) luồng A và trao quyền sử dụng Mutex.
      • Dựa vào 3 nguyên tắc trên, ta có thể sử dụng Mutex để bảo vệ độc quyền tài nguyên hay nội dung biến khi có sự truy cập của luồng.
      • Có thể giải quyết bài toán tranh chấp biến bằng Mutex.
      • /*Hai luồng chạy song song dùng chung biến Mutex X1*/
      • Luồng thứ nhất
          • Khoá Mutex ‘X1’
          • Gán trị 0 cho biến a
          • Gán trị 0 cho biến b
          • Tháo khoá Mutex ‘X1’
      • Luồng thứ hai
          • Khoá Mutex ‘X1’
          • Gán trị 1 cho biến a
          • Gán trị 1 cho biến b
          • Tháo khoá Mutex ‘X1’
      • 8.5.2. Tạo và khởi động Mutex
      • Để tạo đối tượng Mutex  khai báo biến kiểu pthread_mutex_t và khởi tạo giá trị ban đầu cho biến này.
      • Mã khai báo Mutex thường có dạng như sau:
      • pthread_mutex_t a_mutex=PTHREAD_MUTEX_INITIALIZER;
      • Có thể sử dụng hàm pthread_mutex_init() để khởi tạo Mutex:
        • #include <pthread.h>
        • int pthread_mutex_init (pthread_mutex_t*mutex,
        • const pthread_mutexattr_t *mutexattr);
      • Ví dụ:
        • int res;
        • pthread_mutex_t *mutex;
        • res= pthread_mutex_init (mutex, NULL);
        • if (res!=0) {
        • perror(“Initialize mutex fail ”);
        • }
      • 8.5.3. Khóa và tháo khoá cho Mutex
      • Sau khi tạo xong Mutex, thao tác kế tiếp mà luồng thường hay sử dụng là khóa ( lock ) và tháo khóa ( unlock ) cho Mutex.
      • Sử dụng hàm pthread_mutex_lock() để khóa Mutex. Nếu không khóa được  sẽ đặt luồng hiện hành vào trong trạng thái chờ.
      • Cách khóa và tháo khóa Mutex:
      • pthread_mutex_t a_mutex=PTHREAD_MUTEX_INITIALIZER;
      • int rc= pthread_mutex_lock (&a_mutex);
      • if (rc) { /*Loi phat sinh*/
      • perror(“pthread_mutex_lock error”);
      • pthread_exit(NULL);
      • } /* Kh oá Mutex*/
      • rc= pthread_mutex_unlock (&a_mutex);
      • if (rc) {
      • perror(“pthread_mutex_unlock error”);
      • pthread_exit(NULL);
      • } /* T há o kho á Mutex*/
      • 8.5.4. Hủy Mutex
      • Khi sử dụng xong Mutex  cần hủy nó.
      • Hàm pthread_mutex_destroy() để hủy Mutex:
      • rc=pthread_mutex_destroy(&a_mutex);
      • 8.5.5. Sử dụng Mutex
      • Ví dụ 8-5 : thread_race.c (Không dùng Mutex)
      • Ví dụ 8-6 : thread_race2.c (Dùng Mutex)
      • 8.6. Đồng bộ hóa luồng bằng Biến điều kiện
      • Thực tế, cần những phương tiện đồng bộ hóa tinh tế hơn Mutex dùng lập trình ứng dụng Client-Server.
      • 8.6.1. Biến điều kiện là gì?
      • Biến điều kiện ( Condition Variable ) là cơ chế cho phép các luồng chờ (trong trạng thái ngủ và không tiêu thụ thời gian CPU) một tình huống nào đó phát sinh.
      • Nhiều luồng có thể thực hiện chờ trên cùng một biến điều kiện.
      • Khi luồng nào đó phát tín hiệu đến biến điều kiện thì các luồng chờ tại đó sẽ được đánh thức để thực thi tác vụ được yêu cầu.
      • Do biến điều kiện không cung cấp cơ chế khóa tài nguyên nên thường phải sử dụng biến điều kiện chung với đối tượng Mutex.
      • 8.6.2. Tạo và khởi động biến điều kiện
      • Tạo biến điều kiện yêu cầu phải khai báo và định nghĩa biến kiểu cấu trúc pthread_con_t
      • Biến điều kiện có thể được khai báo và khởi tạo như sau:
      • pthread_cond_t got_request=PTHREAD_COND_INITIALIZER;
      • Hoặc bằng cách dùng hàm pthread_cond_init() :
      • int pthread_cond_init (pthread_cond_t *cond, pthread_cond_attr_t *cond_attr);
      • 8.6.3. Gửi tín hiệu đến biến điều kiện
      • Dùng hàm pthread_cond_signal() để đánh thức một luồng nào đó đang chờ trên biến điều kiện.
      • Dùng hàm pthread_cond_broadcast() để đánh thức toàn bộ các luồng đang chờ trên biến điều kiện.
      • 8.6.4. Đợi trên biến điều kiện
      • Việc một luồng phát tín hiệu đến biến điều kiện thì có thể các luồng khác phải chờ bằng cách gọi hàm:
      • pthread_cond_wait() hay pthread_cond_timewait()
      • Mỗi hàm nhận hai đối số, đối số đầu là biến điều kiện để chờ và đối số thứ hai là đối tượng Mutex phải được khóa trước khi gọi hàm chờ. Các hàm này thực hiện tháo khóa Mutex và cho phép luồng chờ đến khi có tín hiệu gửi đến biến điều kiện. Nếu luồng được đánh thức, hàm tự động khóa Mutex trở lại và quay về nơi gọi.
      • Ví dụ sử dụng hàm pthread_cond_wait() chờ trên biến điều kiện (giả sử biến got_request là biến điều kiện đã được khởi tạo)
        • /* Khóa Mutex */
        • int rc= pthread_mutex_lock (&request_mutex);
        • if (rc) { /*Lỗi phát sinh*/
          • perror(“pthread_mutex_lock error”);
          • pthread_exit(NULL);
      • }
      • /* Luồng hiện hành được chuyển sang trạng thái chờ tại got_request và mutex được tháo khóa */
      • rc= pthread_cond_wait (&got_request,&request_mutex);
      • if (rc==0) {
      • /* Luồng vừa được đánh thức do biến điều kiện nhận được tín hiệu. Đối tượng Mutex được pthread_cond_wait() khóa trở lại */
      • /* Thực hiện công việc sau khi thức giấc */
      • . . .
      • }
      • /*Tháo khóa cho Mutex*/
      • pthread_mutex_unlock (&request_mutex);
      • Tham khảo cách sử dụng hàm pthread_cond_timewait() trong tài liệu (trang 351-353)
      • 8.6.5. Hủy biến điều kiện
      • Sau khi sử dụng xong biến điều kiện cần phải hủy nó  giải phóng tài nguyên hệ thống:
        • int rc= pthread_cond_destroy (&got_request);
        • if (rc==EBUSY) {
        • /*Lỗi có thể một luồng nào đó đang dùng biến điều kiện*/
        • perror(“Delete condition variable error”);
        • ...
      • }
      • 8.6.6. Sử dụng biến điều kiện
      • - Giả sử cần xây dựng ứng dụng kiểu Web Server hoặc Database Server với một tập luồng trong hàng đợi. Mỗi yêu cầu được xử lý bởi một luồng được điều ra. Phục vụ xong, luồng được đưa về hàng đợi. Nếu tất cả các luồng đều bận, yêu cầu xử lý có thể bị bỏ lỡ.
      • Vấn đề này được giải quyết bằng cách dùng một biến nguyên lưu lại số lần nhận tín hiệu nhưng chưa xử lý. Mỗi luồng khi chờ trên biến điều kiện đều phải kiểm tra giá trị này. Ví dụ 8-7 : thread_queue.c
      • 8.7. Đồng bộ hóa luồng bằng Semaphore
      • 8.7.1. Semaphore là gì?
      • Semaphore được xem như là một cờ hiệu tương tự Mutex, nếu một luồng cần sử dụng tài nguyên  thông báo với Semaphore bằng cách gọi hàm sem_wait() . Khởi đầu Semaphore sẽ mang giá trị dương. Nếu giá trị này >0, nó được giảm đi 1 và cho luồng sử dụng tài nguyên. Nếu <=0, luồng phải chờ. Semaphore là phương tiện đồng bộ hóa do E.W Dijkstra đề xuất năm 1965.
      • - Khi một luồng sử dụng xong tài nguyên, nó phải gọi gọi sem_post() để trả quyền sử dụng tài nguyên lại cho Semaphore cấp phát cho lần sử dụng khác.
      • Gọi hàm sem_init() để khởi tạo biến Semaphore với kiểu cấu trúc sem_t như sau:
        • #include <semaphore.h>
        • int sem_init(sem_t *sem, int pshared, unsigned int value);
      • 8.7.2. Ứng dụng Semaphore
      • Sử dụng để lập trình bài toán “Sản xuất - Tiêu thụ” ( Producer - Consumer )
      • Mô tả: Có hai luồng chạy song song. Một luồng có chức năng sản xuất ra sản phẩm ( Producer ) và một luồng thì lấy sản phẩm đó để tiêu thụ ( Consumer ). Nếu sản xuất và tiêu thụ cùng nhịp độ thì tốt ( Cầu = Cung ). Nhưng nếu có trường hợp Cầu > Cung (hoặc ngược lại)  Giải quyết ? SỬ DỤNG SEMAPHORE
      • Ví dụ 8-8 : prod_consumer.c (Không dùng Semaphore)
      • Ví dụ 8-9 : prod_consumer2.c (Dùng Semaphore)
      • 8.8. Đặt thuộc tính cho luồng
      • 8.8.1. Thuộc tính của luồng - Tạo luồng tách rời
      • Có những luồng hoạt động một cách độc lập nhằm thực hiện một công việc và không gắn kết với bất kỳ luồng nào  luồng tách rời ( detached thread )
      • Những luồng này được tạo ra bằng cách đặt thuộc tính cho luồng khi gọi hàm pthread_create() hoặc hàm pthread_detach()
      • Hàm khởi tạo thuộc tính cho luồng pthread_attr_init()
      • #include <pthread.h>
      • int pthread_attr_init(pthread_attr_t *attr);
      • /* Hàm trả về 0 nếu đối tượng attr khởi tạo thành công */
      • - Thông thường hàm pthread_attr_init() thường được sử dụng chung với hàm pthread_attr_destroy() dùng để hủy thuộc tính của đối tượng
      • Một số hàm thay đổi thuộc tính sau:
      • #include <pthread.h>
      • int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);
      • int pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy);
      • int pthread_attr_setinheritsched (pthread_attr_t *attr, int inherit);
      • int pthread_attr_setschedparam (const pthread_attr_t *attr,const struct sched_param *param);
      • int pthread_attr_setscope (pthread_attr_t *attr, int scope);
      • int pthread_attr_getscope (const pthread_attr_t *attr, int *scope);
      • Tham khảo Ví dụ 8-10 : thread_detach.c
      • 8.8.2. Điều phối độ ưu tiên của luồng
      • Cách thức đặt độ ưu tiên cho luồng:
      • res= pthread_attr_setschedpolicy (&thread_attr, SCHED_OTHER);
      • if(res!=0) {
      • perror(“Set thread policy error”);
      • exit(EXIT_FAILURE);
      • }
      • /*Lấy về độ ưu tiên T ối đa và Tối thiểu của luồng*/
      • max_priority= sched_get_priority_max (SCHED_OTHER);
      • min_priority= sched_get_priority_min (SCHED_OTHER);
    • /*Đặt lại độ ưu tiên của luồng*/ scheduling_value. sched_priority =min_priority; res= pthread_attr_setschedparam (&thread_attr, &scheduling_value); if(res!=0) { perror(“Set thread priority error”); exit(EXIT_FAILURE); }
      • 8.9. Hủy bỏ và chấm dứt luồng
      • Sử dụng hàm pthread_cancel() để yêu cầu luồng khác chấm dứt khi đang thực thi.
          • #include <pthread.h>
          • int pthread_cancel (pthread_t thread);
      • Hàm trên sẽ gửi tín hiệu đến luồng cần huỷ. Tuỳ theo trạng thái thiết lập mà luồng sẽ quyết định chấm dứt ngay hoặc trì hoãn trong một thời gian trước khi chấm dứt.
      • Trạng thái chấm dứt của luồng được thiết lập bằng hàm:
          • #include <pthread.h>
          • int pthread_setcancelstate (int state,
          • int *oldstate);
      • Hàm pthread_setcanceltype() xác định kiểu chấm dứt của luồng trong trường hợp luồng yêu cầu thiết lập thêm một thông số hủy nữa:
      • #include <pthread.h>
      • int pthread_setcanceltype (int type, int *oldtype);
      • Đối số của type có thể là PTHREAD_CANCEL_ASYNCHRONOUS hoặc PTHREAD_CANCEL_DEFERRED
      • Trong trường hợp 2, yêu cầu chấm dứt luồng chỉ được hoàn tất khi một trong các hàm sau được gọi:
        • pthread_join()
        • pthread_cond_wait()
        • pthread_cond_timewait()
        • pthread_testcancel()
        • sem_wait()
        • sigwait()
      • - Ví dụ 8-11 : thread_cancel.c
      • Chân thành cảm ơn
        • Các anh/chị đã tham dự bài giảng.
        • Bộ phận Multi-media đã hỗ trợ chúng tôi trong quá trình thuyết giảng.
      Kết thúc chương 8