Khai thác tính đa nhiệm, một chương trình có thể lập trình thực hiện nhiều
phần việc đồng thời. Gọi là lập trình đa luồng (thread), có thể gọi đa tuyến.
Luồng là quá trình thực hiện một đơn vị chương trình, độc lập với thực
hiện các đơn vị khác trong chương trình đó.
Mỗi luồng thường gắn với thực hiện một hàm nào đó trong chương trình,
ta gọi hàm này là hàm luồng.
Mỗi chương trình khi chạy luôn có một luồng ứng với thực hiện hàm chính
của chương trình (WinMain), các luồng khác được tạo ra từ luồng này.
9 trang |
Chia sẻ: lylyngoc | Lượt xem: 1785 | Lượt tải: 1
Bạn đang xem nội dung tài liệu Chương 8 Lập trình đa luồng, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
1Chương 8
Lập trình
đa luồng
28.1. Giới thiệu lập trình đa luồng
Khai thác tính đa nhiệm, một chương trình có thể lập trình thực hiện nhiều
phần việc đồng thời. Gọi là lập trình đa luồng (thread), có thể gọi đa tuyến.
Luồng là quá trình thực hiện một đơn vị chương trình, độc lập với thực
hiện các đơn vị khác trong chương trình đó.
Mỗi luồng thường gắn với thực hiện một hàm nào đó trong chương trình,
ta gọi hàm này là hàm luồng.
Mỗi chương trình khi chạy luôn có một luồng ứng với thực hiện hàm chính
của chương trình (WinMain), các luồng khác được tạo ra từ luồng này.
luồng 3
luồng 1 luồng 4
luồng chính (main thread)
luồng 2
thời gian
38.1. Giới thiệu lập trình đa luồng
Mỗi luồng trong chương trình có mức độ ưu tiên thực hiện, là tài nguyên
thời gian máy dành cho luồng.
Ngoài ra mỗi luồng có các tài nguyên như stack, mức độ bảo mật,...
Minh họa một chương trình đa luồng ứng với các hàm:
Có hai loại luồng: luồng làm việc (worker) và luồng giao diện (user
interface). Luồng làm việc chỉ chạy bên trong máy, còn luồng giao diện
cung cấp những tương tác với người dùng.
Luồng chính (main thread)
Luồng 1
Luồng 2
Luồng 3
Luồng 4
Chương trình
Hàm 1
Hàm 2
Hàm 3
48.2. Lập trình luồng làm việc
Luồng làm việc được lập trình bởi một hàm gọi là hàm luồng, sau đó tạo
luồng từ hàm này, gồm hai bước sau:
Bước 1: Lập hàm xử lý luồng (hàm luồng), mẫu hàm khai báo như sau:
UINT tên_hàm ( LPVOID tham_số );
Trong đó tham số sẽ nhận các dữ liệu cho việc thực hiện bên trong hàm luồng,
nó được truyền từ câu lệnh tạo luồng ở bước 2.
Bước 2: Tạo luồng tại thời điểm cần thiết
CWinThread* AfxBeginThread( tên_hàm_luồng , dữ_liệu_truyền );
Có thể quy định các tham số như độ ưu tiên, độ lớn stack,... trong tham số
của lệnh tạo luồng. Mẫu đầy đủ như sau:
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,
LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT
nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES
lpSecurityAttrs = NULL );
Lệnh tạo luồng trả về con trỏ đối tượng của luồng được tạo tương ứng,
kiểu lớp CWinThread. Qua đối tượng này có thể tác động lên các luồng khi
chạy.
58.3. Lập trình luồng giao diện
MFC cung cấp một lớp cho việc lập trình luồng kiểu giao diện, có tên CWinThread,
lớp này có phương thức ảo Run() để chạy ứng với luồng.
Chúng ta xây dựng một lớp kế thừa CWinThread, viết đè hàm Run() để thực hiện
luồng theo mẫu sau, lớp này phải có cơ chế tạo động – DYNCREATE.
class tên_lớp_luồng : public CWinThread
{ public: void Run()
{
..........lập trình hàm chạy luồng.............
}
DECLARE_DYNCREATE()
};
IMPLEMENT_DYNCREATE( tên_lớp_luồng , CWinThread )
Viết lệnh tạo luồng sử dụng lớp trên tại thời điểm mong muốn
CWinThread AfxBeginThread( RUNTIME_CLASS( tên_lớp_luồng ) );
Ngoài ra có thể viết đè các phương thức khác để thực hiện theo yêu cầu như
InitInsance(), ExitInstance(), OnIdle(),... Luồng giao diện giống như luồng chính
của một ứng dụng.
68.4. Một số lệnh liên quan
Hàm kết thúc luồng
void AfxEndThread( UINT nExitCode );
Một số thành viên lớp đối tượng CWinThread để quản lý luồng,
CWinThread::
m_hThread : số hiệu định danh luồng,
m_nThreadID : chỉ số của luồng,
m_pMainWnd : con trỏ đối tượng cửa sổ chính của ứng dụng,
int GetThreadPriority(); lấy độ ưu tiên của luồng,
void SetThreadPriority( int k ); đặt độ ưu tiên luồng,
DWORD SuspendThread(); tạm dừng thực hiện luồng,
DWORD ResumeThread(); tiếp tục chạy luồng,...
78.5. Đồng bộ các luồng
Khi các luồng thực hiện cùng xử lý một tài nguyên nào đó (ví dụ dữ liệu) có
thể dẫn đến xung đột, không nhất quán gọi là không đồng bộ.
Minh họa không đồng bộ giữa các luồng trên một dữ liệu
data
store data
use data
change data
use data
store data
use data
change data
use data
Thread1 Thread2
thời gian
set A
set B
A?
B
inc by C
inc by D
B+C?
A+D?
88.5. Đồng bộ các luồng...
Cách 1: Sử dụng phương pháp dựng cờ, mỗi lần một luồng nào đó cần xử
lý dữ liệu phải chờ trạng thái cờ ở trạng thái tắt, bật trạng thái cờ để xử lý,
xử lý xong tắt trạng thái cờ để luồng khác có thể xử lý tiếp.
Minh họa đoạn chương trình sau:
while (flag); //chờ giá trị cờ cho đến khi bằng 0
flag = 1;
// ... truy xuất tài nguyên dùng chung ở đây ... //
flag = 0;
Cách 2: Dùng đối tượg khóa CSingleLock thực hiện với các phương thức:
CSingleLock( CSyncObject *SyncOb, BOOL InitialState = FALSE);
BOOL CSingleLock :: Lock( DWORD dwDelay=INFINITE);
BOOL CSingleLock :: UnLock();
BOOL CSingleLock :: UnLock( LONG Count, LONG *Previous=NULL);
Đối tượng đồng bộ kiểu CSyncObject, là lớp cơ sở ảo cung cấp cơ chế
đồng bộ giữa các luồng. Các lớp kề thừa gồm CEvent, CMutex,
CCriticalSection, CSemaphore.
98.5. Đồng bộ các luồng...
Các bước thực hiện đồng bộ theo đối tượng trên:
Bước 1: Tạo một đối tượng đồng bộ từ một trong 4 lớp CCriticalSection, CEvent,
CMutex, CSemaphore dùng để điều khiển truy xuất tài nguyên.
Bước 2: Tạo một đối tượng lớp CSingleLock và sử dụng đối tượng đồng bộ đã tạo
ở bước 1 trên.
Bước 3: Để chặn truy xuất tới tài nguyên gọi hàm Lock() trên đối tượng
CSingleLock.
Bước 4: Thực hiện truy xuất tài nguyên.
Bước 5: Gọi UnLock() để hủy bỏ chặn bởi hàm Lock().