Cách sử dụng

Một phần của tài liệu Lập trình luồng (Trang 30 - 62)

Thủ tục pthread_mutex_lock() được sử dụng bởi một thread để thu được một khóa trên một biến mutex xác định. Nếu biến mutex đã được khóa bởi một thread khác, lời gọi này sẽ chặn lời gọi thread cho tới khi mutex được mở khóa.

Sinh viên: Cấn Việt Dũng 31 Lớp : K51CHTTT

Thủ tục pthread_mutex_trylock() sẽ cố thử khóa một mutex. Tuy nhiên, nếu

mutex đã được khóa, thủ tục này sẽ ngay lập tức trả về lỗi code “busy”. Thủ tục này có thể có ích trong việc ngăn ngừa điều kiện bế tắc, như trong hoàn cảnh ưu tiên đảo

ngược ( priority-inversion ).

Thủ tục pthread_mutex_unlock() sẽ mở khóa một mutex nếu được gọi bởi

thread đang sở hữu mutex đó. Việc gọi thủ tục này được yêu cầu sau khi một thread hoàn thành việc sử dụng dữ liệu được bảo vệ nếu thread khác giành được mutex cho công việc của chúng với dữ liệu được bảo vệ. Một lỗi sẽđược trả về nếu:

 Mutex này đã được mở khóa.

 Mutex được sở hữu bởi thread khác.

Không có gì gọi là “ma thuật” về mutexes… trong thực tế, chúng na ná như

một “thỏa thuận của quý ông (gentlement’s agreement)” giữa các thread tham gia.

Đây là thiết lập của người viết chương trình đểđảm bảo rằng những thread cần thiết làm cho các mutex khóa và mở khóa một cách chính xác. Kịch bản sau đây thể hiện một lỗi logic:

Ví dụ sau đây minh họa cho cách sử dụng một biến mutex trong một chương

trình thread . Dữ liệu chính luôn sẵn sàng cho tất cả các thread thông qua một biến struct toàn cục. Mỗi thread làm việc trên một phần dữ liệu khác nhau. Thread chính

đợi cho đến khi tất cả các thread khác hoàn thành xong việc tính toán của chúng, và

sau đó in kết quả tổng: ********************************************************************* #include <pthread.h> #include <stdio.h> #include <stdlib.h> /*

Sinh viên: Cấn Việt Dũng 32 Lớp : K51CHTTT to allow the function "dotprod" to access its input data and

place its output into the structure. */ typedef struct { double *a; double *b; double sum; int veclen; } DOTDATA;

/* Define globally accessible variables and a mutex */ #define NUMTHRDS 4 #define VECLEN 100 DOTDATA dotstr; pthread_t callThd[NUMTHRDS]; pthread_mutex_t mutexsum; /*

The function dotprod is activated when the thread is created. All input to this routine is obtained from a structure

of type DOTDATA and all output from this function is written into this structure. The benefit of this approach is apparent for the multi-threaded program: when a thread is created we pass a single argument to the activated function - typically this argument

is a thread number. All the other information required by the function is accessed from the globally accessible structure. */

void *dotprod(void *arg) {

Sinh viên: Cấn Việt Dũng 33 Lớp : K51CHTTT int i, start, end, len ;

long offset;

double mysum, *x, *y; offset = (long)arg; len = dotstr.veclen; start = offset*len; end = start + len; x = dotstr.a; y = dotstr.b; /*

Perform the dot product and assign result to the appropriate variable in the structure. */

mysum = 0;

for (i=start; i<end ; i++) {

mysum += (x[i] * y[i]); }

/*

Lock a mutex prior to updating the value in the shared structure, and unlock it upon updating.

*/ pthread_mutex_lock (&mutexsum); dotstr.sum += mysum; pthread_mutex_unlock (&mutexsum); pthread_exit((void*) 0); } /*

Sinh viên: Cấn Việt Dũng 34 Lớp : K51CHTTT print out result upon completion. Before creating the threads,

the input data is created. Since all threads update a shared structure, we need a mutex for mutual exclusion. The main thread needs to wait for all threads to complete, it waits for each one of the threads. We specify a thread attribute value that allow the main thread to join with the threads it creates. Note also that we free up handles when they are no longer needed.

*/

int main (int argc, char *argv[]) {

long i;

double *a, *b; void *status; pthread_attr_t attr;

/* Assign storage and initialize values */

a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double)); b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double)); for (i=0; i<VECLEN*NUMTHRDS; i++)

{ a[i]=1.0; b[i]=a[i]; } dotstr.veclen = VECLEN; dotstr.a = a; dotstr.b = b; dotstr.sum=0; pthread_mutex_init(&mutexsum, NULL); /* Create threads to perform the dotproduct */ pthread_attr_init(&attr);

Sinh viên: Cấn Việt Dũng 35 Lớp : K51CHTTT pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); for(i=0; i<NUMTHRDS; i++)

{ /*

Each thread works on a different set of data. The offset is specified by 'i'. The size of

the data for each thread is indicated by VECLEN. */

pthread_create(&callThd[i], &attr, dotprod, (void *)i); }

pthread_attr_destroy(&attr);

/* Wait on the other threads */ for(i=0; i<NUMTHRDS; i++) {

pthread_join(callThd[i], &status); }

/* After joining, print out the results and cleanup */ printf ("Sum = %f \n", dotstr.sum);

free (a); free (b); pthread_mutex_destroy(&mutexsum); pthread_exit(NULL); } 2.4. Biến điều kiện 2.4.1. Khái niệm về biến điều kiện

Biến điều kiện cung cấp một cách nữa cho thread để đồng bộ hóa. Trong khi mutexes thể hiện sự đồng bộ bằng cách điều khiển thread truy cập tới dữ liệu thì biến điều kiện cho phép thread đồng bộ dựa trên giá trị thực của dữ liệu. Nếu không có biến điều kiện, người lập trình sẽ cần phải có thread polling một cách liên tục (có

Sinh viên: Cấn Việt Dũng 36 Lớp : K51CHTTT

thể trong một phần quan trọng), để kiểm tra xem nếu điều kiện được đáp ứng. Điều này có thể rất tốn tài nguyên kể từ khi thead bận rộn liên tục trong hoạt động này. Một biến điều kiện là một cách để đạt được cùng một mục tiêu mà không cần polling. Một biến điều kiện sẽ luôn được sử dụng cùng với một khóa mutex. Một chuỗi đại diện cho việc sử dụng các biến điều kiện được hiển thị trong bảng sau:

Thread chính:

- Khai báo và khởi tạo dữ liệu/biến toàn cục cần đồng bộ hóa (ví dụ

“count”).

- Khai bảo và khởi tạo biến điều kiện. - Khai bảo và khởi tạo mutex liên quan. - Tạo thread A và B để làm việc

Thread A

- Làm việc tới khi một điều kiện nhất

định phải xảy ra.(ví dụ khi biến count

đạt được một giá trị cụ thểnào đấy) - Khóa mutex liên quan và kiểm tra giá trị của biến toàn cục.

- Gọi thủ tục pthead_cond_wait() để

thực hiện việc ngăn chặn đợi tín hiệu từ thread B. Chú ý rằng, một lời gọi tới hàm pthead_cond_wait() sẽ tự động mở khóa cho mutex liên quan để

nó có thểđược sử dụng bởi thread B. - Khi đã nhận được tín hiệu,thread A sẽ “wake up”. Mutex sẽ tự động bị

khóa.

- Mở khóa mutex.

Thread B

- Làm việc

- Khóa mutex liên quan.

- Thay đổi giá trị của biến toàn cụng để thread A chờ.

- Kiểm tra giá trị của biến toàn cục mà thread A chờ. Nếu nó thỏa mãn

điều kiện mong muốn, tín hiệu cho thread A.

- Mở khóa mutex. - Tiếp tục

Sinh viên: Cấn Việt Dũng 37 Lớp : K51CHTTT - Tiếp tục. Thread chính: - Nối / Tiếp tục 2.4.2. Tạo ra và phá hủy 1 biến điều kiện 2.4.2.1. Các thủ tục

 Pthread_cond_init (condition, attr)

 Pthread_cond_destroy(condition)

 Pthead_condattr_init(attr)

 Pthread_condattr_destroy(attr)

2.4.2.2. Cách sử dụng

Biến điều kiện phải được khai báo với kiểu pthread_cond_t, và phải được khởi tạo trước khi chúng được sử dụng. Có hai cách để khởi tạo biến điều kiện:

 Theo cách tĩnh, khi chúng được khai báo: pthread_cond_t myconvar = PTHREAD_COND_INITIALIZER;

 Theo cách động, với thủ tục pthread_cond_init. ID của biến điều kiện được tao

ra được trả về với lời gọi thread thông qua tham sốcondition. Phương thức này cho phép thiết lập thuộc tính cho biến điều kiện, attr.

Đối tượng tùy chọn attr được sử dụng để thiết lập thuộc tính cho biến điều kiện. Chỉ có một thuộc tính được định nghĩa cho biến điều kiện là process-shared, cho phép biến điều kiện được nhìn bởi các thread trong tiến trình khác. Đối tượng thuộc tính, nếu được sử dụng, phải có kiểu pthread_condattr_t (để là NULL nếu chấp nhận thuộc tính mặc định). Chú ý rằng không phải tất cả các thể hiện đểu có thể cung cấp thuộc tính này. Các thủ tục pthead_condattr_init() và

pthread_condattr_destroy() được sử dụng để tạo ra và phá hủy đối tượng thuộc tính của biến điều kiện. Thủ tục pthread_cond_destroy() nên được sử dụng để giải phóng biến điều kiện nếu nó không còn cần thiết nữa.

Sinh viên: Cấn Việt Dũng 38 Lớp : K51CHTTT

2.4.3.1. Các thủ tục

 Pthread_cond_wait (condition, mutex)

 Pthread_cond_signal(condition)

 Pthread_cond_broadcast(condition)

2.4.3.2. Cách sử dụng

Thủ tục pthread_cond_wait() ngăn chắn lời gọi thread cho tới khi điểu kiện

(condition) xác định được báo hiệu. Thủ tục này nên được gọi trong khi mutex bị

khóa và nó sẽ tựđộng mở khóa mutex trong khi chờ đợi. Sau khi tín hiệu nhận được và thread tiếp tục, mutex sẽ tự động bị khóa để sử dụng bởi thread. Người lập trình

sau đó chịu trách nhiệm mở khóa khi thread hoàn thành với nó.

Thủ tục pthead_cond_singal() được dùng để báo hiệu (hoặc đánh thức) thread khác đang đợi trên biến điều kiện. Nó nên được gọi sau khi biến mutex bị khóa, và phải mởi khóa mutex theo thứ tự cho thủ tục pthread_cond_wait() được hoàn thành. Thủ tục pthread_cond_broadcast() nên được sử dụng thay vì pthread_cond_signal () nếu có nhiều hơn một thread đang ở trong trạng thái chờ.

Sẽ có một lỗi logic nêu gọi pthread_cond_signal trước khi gọi pthread_cond_wait().

Ví dụ đơn giản để làm rõ cách sử dụng của một vài biến điều kiện Pthread. Trong hàm main() tạo ra ba thread. Hai trong sốđó thực hiện việc và cập nhật biến. Thread thứba đợi cho đến khi biến count nhận được giá trị xác định.

#include <pthread.h> #include <stdio.h> #include <stdlib.h> #define NUM_THREADS 3 #define TCOUNT 10 #define COUNT_LIMIT 12 int count = 0; int thread_ids[3] = {0,1,2}; pthread_mutex_t count_mutex;

Sinh viên: Cấn Việt Dũng 39 Lớp : K51CHTTT pthread_cond_t count_threshold_cv;

void *inc_count(void *t) {

int i;

long my_id = (long)t; for (i=0; i<TCOUNT; i++) {

pthread_mutex_lock(&count_mutex); count++;

/*

Check the value of count and signal waiting thread when condition is reached. Note that this occurs while mutex is locked.

*/

if (count == COUNT_LIMIT) {

pthread_cond_signal(&count_threshold_cv);

printf("inc_count(): thread %ld, count = %d Threshold reached.\n", my_id, count); }

printf("inc_count(): thread %ld, count = %d, unlocking mutex\n", my_id, count); pthread_mutex_unlock(&count_mutex);

/* Do some "work" so threads can alternate on mutex lock */ sleep(1); } pthread_exit(NULL); } void *watch_count(void *t) {

long my_id = (long)t;

printf("Starting watch_count(): thread %ld\n", my_id);

Sinh viên: Cấn Việt Dũng 40 Lớp : K51CHTTT Lock mutex and wait for signal. Note that the pthread_cond_wait

routine will automatically and atomically unlock mutex while it waits. Also, note that if COUNT_LIMIT is reached before this routine is run by the waiting thread, the loop will be skipped to prevent pthread_cond_wait from never returning.

*/

pthread_mutex_lock(&count_mutex); if (count<COUNT_LIMIT) {

pthread_cond_wait(&count_threshold_cv, &count_mutex);

printf("watch_count(): thread %ld Condition signal received.\n", my_id); count += 125;

printf("watch_count(): thread %ld count now = %d.\n", my_id, count); }

pthread_mutex_unlock(&count_mutex); pthread_exit(NULL);

}

int main (int argc, char *argv[]) {

int i, rc;

long t1=1, t2=2, t3=3; pthread_t threads[3]; pthread_attr_t attr;

/* Initialize mutex and condition variable objects */ pthread_mutex_init(&count_mutex, NULL); pthread_cond_init (&count_threshold_cv, NULL);

/* For portability, explicitly create threads in a joinable state */ pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&threads[0], &attr, watch_count, (void *)t1);

Sinh viên: Cấn Việt Dũng 41 Lớp : K51CHTTT pthread_create(&threads[1], &attr, inc_count, (void *)t2);

pthread_create(&threads[2], &attr, inc_count, (void *)t3); /* Wait for all threads to complete */

for (i=0; i<NUM_THREADS; i++) { pthread_join(threads[i], NULL); }

printf ("Main(): Waited on %d threads. Done.\n", NUM_THREADS); /* Clean up and exit */

pthread_attr_destroy(&attr);

pthread_mutex_destroy(&count_mutex); pthread_cond_destroy(&count_threshold_cv); pthread_exit(NULL);

}

2.5. Dữ liệu riêng của Thread(Thread – specific data) 2.5.1. Khái niệm dữ liệu riêng của thread 2.5.1. Khái niệm dữ liệu riêng của thread

Trong cơ chế dữ liệu thread cụ thể, chúng ta có khái niệm về các khóa và giá trị. Mỗi khóa có một tên, và trỏ tới một vài vùng nhớ. Những khóa với cùng tên trong hai thread đồng thời luôn luôn trỏ tới những vùng nhớ khác nhau, điều này

được quản lý bởi thư viện các hàm để bố trí các khối bộ nhớ để truy cập thông qua các khóa này. Chúng ta có một hàm để tạo ra khóa (được gọi một lần mỗi tên khóa trong toàn bộ quá trình), một hàm để cấp phát bộ nhớ(được gọi đồng thời trong mỗi thread), các hàm để ngừng cấp phát bộ nhớ cho các thread cụ thể và một hàm để phá hủy khóa. Chúng ta cũng có những hàm để truy cập tới dữ liệu được trỏ bởi một khóa, cũng như thiết lập giá trị, hoặc trả về giá trị mà nó trỏ tới.

2.5.2. Cấp phát dữ liệu riêng của thread

Hàm pthread_key_create() được dùng để cấp phát một khóa mới. Khóa này bây giờ sẽ trở nên có giá trị cho tất cả các thread trong tiến trình. Khi một khóa được tạo ra, giá trị mặc định là NULL. Sau đó trong mỗi thread có thểthay đổi giá trịnhư

Sinh viên: Cấn Việt Dũng 42 Lớp : K51CHTTT

/* rc được dùng để lưu giá trị trả về của hàm pthread_key_create() */ int rc;

/* định nghĩa 1 biến để nắm giữ khóa, tạo ra 1 lần */ pthread_key_t list_key;

/* cleanup_list là một hàm có thể làm sạch dữ liệu */ extern void* cleanup_list(void*);

/* Tạo ra khóa */

rc = pthread_key_create(&list_key, cleanup_list);

Chú ý sau khi hàm pthread_key_create() trả về, biến list_key sẽ trỏ tới cái

khóa vừa mới được tạo ra. Con trỏ hàm trong tham số thứ hai của hàm pthread_key_create() sẽ tự động được gọi bởi thư viện Pthread khi thoát ra khỏi

thread, với một con trỏ trỏ đến giá trị của khóa như là một tham số. Chúng ta cũng

có thể dùng con trỏ NULL và khi đó sẽ không có hàm nào được trả về cho khóa.

Chú ý rằng, các hàm được gọi một lần trong mỗi thread, kể cả khi chúng ta tạo ra khóa này chỉ một lần, trong một thread. Nếu chúng ta tạo ra vài khóa, hàm destructor liên quan sẽ được gọi theo một trật tự tùy ý, bất kể thứ tự của các khóa tạo

ra.

Nếu hàm pthread_key_create() thành công, nó sẽ trả về giá trị 0. Ngược lại, nó

sẽ trả về vài dòng lỗi. Có một giới hạn PTHREAD_KEYS_MAX() có thể tồn tại

trong quá trình tại bất kỳ thời điểm nào. Mọi cố gắng để tạo ra khóa sau khi thoát

PTHREAD_KEYS_MAX sẽ gặp phải một giá trị trả về là EAGAIN từ hàm pthread_key_create().

2.5.3. Truy cập vào dữ liệu riêng của thread

Sau khi tạo ra một khóa, chúng ta có thể truy cập vào giá trị của chúng bằng cách sử dụng hai hàm: pthread_getspecific() và pthread_setspecific() . Hàm đầu tiên

được dùng để lấy giá trị của một khóa được đưa ra còn hàm sau được dùng để thiết lập giá trị của khóa được đưa ra. Giá trị của một khóa chỉ là một con trỏ kiểu void

Sinh viên: Cấn Việt Dũng 43 Lớp : K51CHTTT

dụng của những hàm này. Giả sử rằng ‘a_key’ là một biến khởi tạo kiểu

pthread_key_t để chứa khóa được tạo ra trước:

/* biến này được sử dụng để lưu giá trị trả về của hàm */ int rc;

/* định nghĩa một biến mà chúng ta sẽ lưu vào đó một vài dữ liệu, ví dụ là 1 số

nguyên */

int* p_num = (int*)malloc(sizeof(int)); if (!p_num) {

fprintf(stderr, "malloc: out of memory\n"; exit(1);

}

/* khởi tạo giá trị */

(*p_num) = 4;

/* bây giờ lưu giá trị này vào khóa */

/* chú ý rằng chúng ta không lưu ‘p_num’ vào khóa */

/* chúng ta chỉ lưu giá trị mà p_num trỏ tới */

rc = pthread_setspecific(a_key, (void*)p_num);

.

/* lấy giá trị của khóa a_key và in ra */ {

int* p_keyval = (int*)pthread_getspecific(a_key); if (p_keyval != NULL) {

Sinh viên: Cấn Việt Dũng 44 Lớp : K51CHTTT

printf("value of 'a_key' is: %d\n", *p_keyval); }

}

Chú ý rằng, nếu chúng ta thiết lập giá trị cho khóa trong một thread, và cố thử

lấy giá trị trong thread khác, chúng ta sẽ nhận được giá trị NULL, giá trị này là khác biệt cho mỗi thread. Cũng có hai trường hợp mà hàm pthread_getspecific() trả về

NULL:

- Khóa được cung cấp như tham số không hợp lệ ( ví dụ như khóa chưa được tạo).

- Giá trị của khóa là NULL. Điều này có thể là nó chưa được khởi tạo, hoặc

được đặt là NULL bởi lời gọi pthread_setspecific() trước.

2.5.4. Xóa dữ liệu trong thread

Hàm pthread_key_delete() có thểđược dùng để xóa khóa. Nhưng không được nhầm lẫn với tên của hàm này: nó không xóa được vùng nhớ liên quan tới khóa, cũng không gọi hàm hủy được định nghĩa trong khi khóa được tạo ra. Vì vậy, vẫn cần giải phóng vùng nhớ nếu cần thiết.

Sử dụng hàm này rất đơn giản. Giải sử list_key là một biến kiểu pthread_key_t

Một phần của tài liệu Lập trình luồng (Trang 30 - 62)

Tải bản đầy đủ (PDF)

(62 trang)