SlideShare a Scribd company logo
Multi-Threaded Programming
การเขียนโปรแกรมให้ทำงานแบบมัลติเทรดด้วยภาษา C/C++
การทำงานแบบมัลติเทรด
แอปพลิเคชัน (Application) หรือโปรแกรมในระดับผู้ใช้(User-Level Program) ที่ทำงานภายใต้การจัดการของระบบ
ปฏิบัติการ Linux จะเรียกว่า "โปรเซส" (Process) หลาย ๆ โปรเซสสามารถทำงานได้อิสระจากกัน ดังนั้นจึงเป็นการ
ทำงานแบบหลายโปรเซส (Multi-Processing)
โปรเซสก็สามารถสร้างโปรเซสอื่นอีกให้เริ่มต้นทำงานใหม่ได้เช่นกัน เป็นความสัมพันธ์ระหว่าง โปรเซสที่สร้าง (Parent
Process) กับโปรเซสที่ถูกสร้าง (Child Process) และเรียกขั้นตอนนี้ว่า Process forking / spawning
ในแต่ละโปรเซสก็สามารถแบ่งออกเป็นการทำงานย่อย ๆ เรียกว่า "เทรด" (Thread) มีหลายเทรดทำงานได้ไปได้พร้อม
ๆ กัน (Multi-Threading) หรือเรียกว่า Concurrency ซึ่งจะมีประโยชน์และช่วยเพิ่มประสิทธิภาพในการทำงานของ
ระบบเนื่องจากคอมพิวเตอร์มีตัวประมวลผลมากกว่าหนึ่งตัวได้(Multi-Processor / Multi-Core CPU) และเป็นการ
เพิ่มระดับการทำงานแบบขนานกันในระดับฮาร์ดแวร์ (Parallel Processing)
การทำงานแบบหลายเทรดนั้น สามารถใช้งานได้ทั้งแบบมีซีพียูเดียว (Single-CPU) โดยการแบ่งช่วงเวลากันทำงาน
ระหว่างเทรด ให้ดูเสมือนว่าทำงานไปได้พร้อม ๆ กัน หรือใช้กับการทำงานของซีพียูที่มีหลายแกน (Multi-Core CPU)
ก็ได้
แต่ถ้ามีการนำคอมพิวเตอร์มาใช้งานร่วมกันในระบบเครือข่ายที่เชื่อมต่อกันด้วยความเร็วสูง และเขียนโปรแกรมเพื่อให้
ทำงานร่วมกัน ให้เป็นส่วนหนึ่งของแอปพลิเคชันเดียวกัน ก็จะเรียกรูปแบบนี้ว่า การประมวลผลแบบกระจาย
(Distributed Processing / Computing)
เราสามารถเขียนโปรแกรมเพื่อให้มีมากกว่าหนึ่งเทรด และสามารถทำงานได้อิสระจากกัน แต่บางกรณีก็ต้องมีการสื่อสาร
กันระหว่างเทรด หรือรอจังหวะการทำงานซึ่งกันและกัน (Inter-Thread Communication & Synchronization)
ถ้าเปรียบเทียบการทำงานระหว่างโปรเซสกับเทรดโดยการจัดการของระบบปฏิบัติการ (OS) การสร้างเทรดขึ้นมาใหม่นั้น
มีขั้นตอนและการใช้ทรัพยากรของระบบ (หรือเรียกว่า Overhead) น้อยกว่าการสร้างโปรเซส เช่น เทรดที่ถูกสร้างขึ้น
ภายใต้โปรเซสเดียวกันจะแชร์การใช้หน่วยความจำเดียวกัน แต่ถ้าเป็นการสร้างโปรเซสใหม่ จะต้องมีการแยกหน่วย
ความจำในการใช้งานระหว่างโปรเซสเหล่านั้น
ในกรณีที่เขียนโปรแกรมด้วยภาษา C/C++ สำหรับ Linux ก็สามารรถใช้ไลบรารีชื่อว่า POSIX Pthreads (ตามมาตรฐาน
(POSIX.1-2001 และ POSIX.1-2008) สำหรับการสร้างและจัดการทำงานของเทรดได้ตัวอย่างฟังก์ชันการทำงานที่
เกี่ยวกับเทรด เช่น
การสร้างเทรดใหม่ (Thread creation)
การจบการทำงานของเทรด (Thread termination)
การทำงานแบบรอจังหวะกันระหว่างเทรด (Thread synchronization)
การจัดลำดับการทำงานของเทรด (Thread scheduling)
การจัดการข้อมูลของเทรดและปฏิสัมพันธ์กับโปรเซส (Thread data management and process interaction)
ในกรณีที่เขียนโปรแกรมด้วยภาษาอื่น อย่างเช่น Java หรือ Python ก็มีไลบรารีให้ใช้งานสำหรับการทำงานแบบมัลติ
เทรดเช่นกัน
การทำงานของเทรดภายใต้โปรเซสเดียวกัน มีการแชร์หรือใช้ทรัพยากรร่วมกันระหว่างเทรด แต่ก็มีสิ่งที่แต่ละเทรดนั้น
ใช้งานแยกกัน เช่น
Thread ID เป็นหมายเลขที่แตกต่างกันและใช้ระบุเทรดแต่ละเทรด
Thread priority เป็นระดับความสำคัญของเทรดและใช้ในการจัดลำดับการทำงานของเทรด
Set of registers, stack pointer (SP) เป็นหน่วยความจำที่ใช้เก็บค่าของรีจิสเตอร์ต่าง ๆ ของซีพียูและเกี่ยวข้อง
กับการทำงานของเทรด เนื่องจากมีการแชร์การใช้งานซีพียูร่วมกัน ข้อมูลเหล่านี้จะถูกจัดเก็บลงในหน่วยความจำที่
เรียกว่า Stack ของแต่ละเทรด ดังนั้นจึงต้องมีตัวระบุหรือชี้ตำแหน่งบนสุดของ Stack ดังกล่าว
Stack for local variables, return addresses เป็นหน่วยความจำแบบ Stack ที่ใช้เก็บข้อมูลในขณะที่เทรด
กำลังทำงาน เช่น เก็บค่าตัวแปรภายในเมื่อมีการเรียกใช้ฟังก์ชันและจบการทำงานของฟังก์ชัน เป็นต้น
เนื่องจากในการทำงานแบบมัลติเทรด จะต้องมีการแบ่งเวลาหรือสลับเวลาในการทำงานกันระหว่างเทรด ดังนั้นจึงต้องมี
การบันทึกสถานะการทำงานของเทรดและเรียกคืนกลับมาทำงานต่อ การสลับการทำงานของเทรด ก็เรียกว่า Thread
Context Switching
ถ้าจะลองเขียนโค้ดโดยใช้ภาษา C++ ก็มีความแตกต่างจากโค้ดภาษา C ออกไปบ้าง แต่ก็สามารถใช้คำสั่งหรือฟังก์ชัน
ของไลบรารี POSIX Pthreads ได้เช่นกัน หรือจะใช้คลาส std::thread (สำหรับเวอร์ชัน C++11 C++14 และ C++17)
นอกจากนั้นแล้วถ้าลองสืบค้นในอินเทอร์เน็ต จะพบว่า มีนักพัฒนาซอฟต์แวร์ได้สร้างคลาส C++ ทำหน้าที่เป็นตัวครอบ
การใช้งานคำสั่งต่าง ๆ ของ POSIX Pthreads ไว้เป็นตัวอย่างหรือนำไปทดลองใช้งานได้โดยแชร์โค้ดไว้ใน Github
เป็นต้น
ตัวอย่างโค้ด C สาธิตการใช้งาน Pthreads
เนื้อหาในส่วนนี้นำเสนอตัวอย่างการเขียนโค้ดภาษา C โดยใช้ไลบรารี POSIX Pthreads สาธิตการทำงานแบบมัลติ
เทรดในรูปแบบต่าง ๆ
ตัวอย่างที่ 1: Thread Creation
โค้ดตัวอย่างแรกนี้ สาธิตการเขียนโปรแกรม เพื่อสร้างเทรดจำนวน 2 เทรด (Thread 1 และ Thread 2) โดยใช้คำสั่ง
pthread_create(...) ซึ่งจะให้ค่ากลับคืนเป็น 0 ถ้าสร้างเทรดใหม่ได้สำเร็จ
ในขั้นตอนการทำงานของเทรด จะต้องมีการสร้างฟังก์ชันตามรูปแบบที่กำหนดไว้โดย POSIX Pthreads ดังนี้
int pthread_create( pthread_t *thread,
1
const pthread_attr_t *attr,
2
void *(*start_routine)(void*),
3
void *arg );
4
ในตัวอย่างนี้ใช้ชื่อฟังก์ชัน thread_entry_func(...) สำหรับ Thread Start Routine และเป็นตัวกำหนดฟังก์ชันการ
ทำงานของเทรด ทั้งสองเทรดในตัวอย่างนี้ มีการใช้ฟังก์ชันดังกล่าวร่วมกัน และเมื่อสร้างเทรดขึ้นมาแล้ว จะใช้ตัวแปรที่
มีชนิดข้อมูลเป็น pthread_t เป็นตัวอ้างอิงแต่ละเทรด
ในตัวอย่างนี้ฟังก์ชันนี้รับค่าอาร์กิวเมนต์มาเป็นพอยน์เตอร์แบบ (void *) แต่แปลงให้เป็นเลขจำนวนเต็มชนิด long เพื่อ
ใช้จำแนกว่า เป็นเทรดหมายเลขใด เมื่อทำคำสั่งต่าง ๆ ของฟังก์ชันนี้ จะมีการรอเวลาให้ผ่านไปโดยใช้คำสั่ง sleep(...)
ระยะเวลาในการรอหน่วยเป็นวินาที จะได้จากการใช้คำสั่ง rand(...) เพื่อสุ่มค่าตัวเลขจำนวนเต็ม โดยกำหนดให้มีค่าอยู
ในช่วง 1..10 วินาที เมื่อรอเวลาตามที่กำหนดไว้แล้ว การทำงานของเทรดจึงจบลง (แต่ละเทรดจะใช้เวลารอไม่เหมือน
กัน เพราะอาจสุ่มเลขได้ค่าต่างกัน ดังนั้นจึงจบการทำงานไม่พร้อมกัน)
การทำงานของโปรแกรมนี้ เมื่อได้สร้างเทรดใหม่ทั้งสองแล้ว จากนั้นจะต้องรอให้เทรดทั้งสองจบการทำงานก่อน จึงจะ
จบการทำงานของโปรแกรม การรอคอยให้เทรดของโปรเซสจบการทำงานนั้น จะต้องใช้คำสั่ง pthread_join(...) ดังนั้น
จึงเป็นการเรียกฟังก์ชันแบบ Blocking Call หรือ รอไปแบบไม่มีเวลาจำกัดจนกว่าเงื่อนไขจะเป็นจริง
// $ gcc -Wall main.c -o main -lpthread
1
2
#include <stdio.h>
3
#include <stdlib.h> // for srand(), rand()
4
#include <unistd.h> // for sleep(), usleep()
5
#include <pthread.h> // the header file for the pthread lib
6
7
void *thread_entry_func( void *arg ) {
8
long id = (long)arg;
9
printf( "Thread started: %ldn", id );
10
// sleep for some seconds (randomized between 1..10)
11
sleep( 1 + (rand() % 10) );
12
printf( "Thread finished: %ldn", id );
13
return NULL;
14
}
15
16
int main( int argc, char *argv[] ) {
17
int retval;
18
pthread_t thread1, thread2;
19
20
// initialize the pseudorandom generator with a seed
21
srand( time(NULL) );
22
23
// create Thread 1
24
retval = pthread_create(
25
&thread1 /*used to identify thread*/,
26
NULL /*default attributes*/,
27
thread_entry_func /*start routine*/,
28
(void*) 1 /*thread argument*/ );
29
printf( "Create thread 1: %sn",
30
retval ? "FAILED" : "OK" );
31
32
// create Thread 2
33
retval = pthread_create(
34
&thread2 /*used to identify thread*/,
35
NULL /*default attributes*/,
36
thread_entry_func /*start routine*/,
37
(void*) 2 /*thread argument*/ );
38
printf( "Create thread 2: %sn",
39
retval ? "FAILED" : "OK" );
40
41
usleep( 10000 /*usec*/ ); // sleep for 10msec
42
43
// wait until thread 1 and 2 are finished.
44
printf( "Waiting for threads to be finishedn" );
45
if (thread1) {
46
pthread_join( thread1, NULL ); // wait for thread 1
47
}
48
if (thread2) {
49
pthread_join( thread2, NULL ); // wait for thread 2
50
}
51
printf( "Done...nn" );
52
return 0;
53
}
54
รูปต่อไปนี้แสดงตัวอย่างการทำคำสั่งแบบ Command line เพื่อคอมไพล์โค้ดชื่อ main.c ที่มีโค้ดในตัวอย่างให้เป็น
ไฟล์ executable ที่มีชื่อว่า main จากนั้นจึงรันโปรแกรมดังกล่าว
ถ้าต้องการจะกำหนดคุณสมบัติของเทรด (pthread_attr_t) ที่จะถูกสร้างขึ้นใหม่โดยใช้คำสั่ง pthread_attr_init(...) ก็มี
ตัวอย่างดังนี้ เช่น การกำหนดให้เทรดมีสถานะเป็น joinable และการเพิ่มขนาดของ Stack สำหรับการทำงานของเทรด
เป็นต้น
// $ gcc -Wall main.c -o main -lpthread
1
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <unistd.h> // for sleep(), usleep()
5
#include <pthread.h> // the header file for the pthread lib
6
7
void *thread_entry_func( void *arg ) {
8
printf( "Thread is active.n" );
9
return NULL;
10
}
11
12
int main( int argc, char *argv[] ) {
13
int retval;
14
size_t stack_size;
15
pthread_attr_t attr;
16
pthread_t thread;
17
18
// create a thread attribute object
19
retval = pthread_attr_init(&attr);
20
if (retval) {
21
printf( "Error while creating thread attribute!n" );
22
exit(1);
23
}
24
// use a joinable thread
25
pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
26
// get the default stack size
27
pthread_attr_getstacksize( &attr, &stack_size );
28
printf( "Default thread stack size: %ldn", stack_size );
29
// set the stack size (use a larger value, say 4x)
30
pthread_attr_setstacksize( &attr, 4*stack_size );
31
32
// create Thread
33
retval = pthread_create(
34
&thread, &attr, thread_entry_func, NULL );
35
printf( "Create thread: %sn",
36
retval ? "FAILED" : "OK" );
37
38
sleep(1);
39
// get the thread stack size
40
pthread_attr_getstacksize( &attr, &stack_size );
41
printf( "Thread stack size: %ldn", stack_size );
42
pthread_attr_destroy( &attr ); // destroy attribute
43
44
if (thread) {
45
pthread_join( thread, NULL ); // wait for thread
46
}
47
printf( "Done...nn" );
48
return 0;
49
}
50
ถัดไปเป็นการสร้างเทรดตามจำนวนที่กำหนดไว้โดย NUM_THREADS แต่มีลักษณะการทำงานเหมือนโค้ดในตัวอย่าง
แรก
#include <stdio.h>
1
#include <stdlib.h> // for srand(), rand()
2
#include <unistd.h> // for sleep(), usleep()
3
#include <pthread.h> // the header file for the pthread lib
4
5
#define NUM_THREADS (10)
6
7
void *thread_entry_func( void *arg ) {
8
	 long id = (long)arg;
9
	 printf( "Thread started: %ld (0x%08lX)n", id, pthread_self() );
10
	 // sleep for some seconds (randomized between 1..10)
11
	 sleep( 1 + (rand() % 10) );
12
	 printf( "Thread finished: %ldn", id );
13
	 return NULL;
14
}
15
16
int main( int argc, char *argv[] ) {
17
	 int retval;
18
	 pthread_t threads[ NUM_THREADS ];
19
	
20
	 // initialize the pseudorandom generator with a seed
21
	 srand( time(NULL) );
22
23
	 // create a number of threads
24
	 for ( int i=0; i < NUM_THREADS; i++ ) {
25
	 	 long id = (i+1); // used as thread argument
26
	 	 retval = pthread_create(
27
&threads[i], NULL,
28
thread_entry_func,
29
(void*) id );
30
printf( "Create thread %ld: %sn", id,
31
	 retval ? "FAILED" : "OK" );
32
	 if ( retval ) { // thread creation error
33
	 	 	 printf( "Program exited...n" );
34
	 	 	 exit(1);
35
	 	 }
36
}
37
38
	 usleep( 1000 /*usec*/ ); // sleep for 1msec before proceeding
39
	
40
	 // wait until all threads are finished.
41
	 printf( "Waiting for all threads to be finishedn" );
42
	 for ( int i=0; i < NUM_THREADS; i++ ) {
43
pthread_join( threads[i], NULL ); // wait for thread
44
	 }
45
	 printf( "Done...nn" );
46
	 return 0;
47
}
48
ตัวอย่างที่ 2: Binary Semaphore
โค้ดในตัวอย่างนี้สาธิตการใช้งานสิ่งที่เรียกว่า "เซมาฟอร์แบบไบนารี" (Binary Semaphore) เพื่อใช้ส่งสัญญาณสื่อสาร
กันระหว่างเทรด เช่น ให้เทรดหนึ่งรอสัญญาณจากอีกเทรดหนึ่ง หรือในกรณีที่จะต้องมีการใช้ทรัพยากรร่วมกัน
คำสั่งที่เกี่ยวข้องกับการใช้งานเซมาฟอร์แบบไบนารีของ POSIX Pthreads มีดังนี้
sem_init(...) ตั้งค่าเริ่มต้นของตัวนับและเปิดใช้งานเซมาฟอร์
sem_wait(..) รอการใช้งานเซมาฟอร์ ถ้าได้ให้ลดค่าลงจาก 1 เป็น 0 แต่ถ้ายังไม่ได้ให้รอไปก่อน
sem_post(...) เพิ่มค่าเซมาฟอร์ให้เป็น 1 ทันทีโดยไม่ต้องรอ
sem_destroy(...) เลิกใช้งานเซมาฟอร์
เซมาฟอร์แบบไบนารีจะมีค่าเริ่มต้นเป็น 1 เมื่อมีการเข้าใช้งานโดยเทรดก็จะลดค่าลงเป็น 0 โดยการทำคำสั่ง
sem_wait(...) แต่ถ้ามีค่าเป็น 0 แล้วและมีเทรดใดต้องการใช้งาน จะต้องรอไปก่อน เมื่อใช้งานเซมาฟอร์แล้วก็ต้องเพิ่ม
ค่าให้กลับคืนเป็น 1 ด้วยคำสั่ง sem_post(...)
ข้อสังเกต: ถ้าให้ตัวนับของเซมาฟอร์มีค่าเริ่มต้นมากกว่า 1 เราจะเรียกว่า "เซมาฟอร์แบบนับ" (Counting Semaphore)
// $ gcc -Wall main.c -o main -lpthread
1
2
#include <stdio.h>
3
#include <stdlib.h>
4
#include <unistd.h> // for sleep(), usleep()
5
#include <pthread.h> // the header file for the pthread lib
6
#include <semaphore.h>
7
8
#define NUM_THREADS (10)
9
10
// global variable
11
sem_t semaphore;
12
13
void *thread_entry_func( void *arg ) {
14
long id = (long)arg;
15
sleep(1);
16
sem_wait(&semaphore); // acquire the semaphore
17
printf( "Thread #%ld is active.n", id );
18
sem_post(&semaphore); // release the semaphore
19
return NULL;
20
}
21
22
int main( int argc, char *argv[] ) {
23
int retval;
24
pthread_t threads[ NUM_THREADS ];
25
26
// create a binary semaphore
27
sem_init( &semaphore, 0, 1 /*initial value*/ );
28
29
// create Threads
30
for (int i=0; i < NUM_THREADS; i++) {
31
	 retval = pthread_create(
32
&threads[i], NULL,
33
thread_entry_func,
34
(void*)(i+1L) );
35
	 printf( "Create thread: %sn",
36
	 retval ? "FAILED" : "OK" );
37
	 if (retval) {
38
	 	 exit(1);
39
	 }
40
}
41
sleep(1);
42
43
for (int i=0; i < NUM_THREADS; i++) {
44
	 if (threads[i]) {
45
	 	 pthread_join( threads[i], NULL ); // wait for thread
46
	 }
47
}
48
sem_destroy( &semaphore ); // destroy semaphore
49
printf( "Done...nn" );
50
return 0;
51
}
52
ตัวอย่างที่ 3: Producer-Consumer
การทำงานร่วมกันระหว่างเทรดตามรูปแบบที่เรียกว่า Producer-Consumer Pattern คือ ให้มีเทรดหนึ่งทำหน้าที่สร้าง
ข้อมูล (Producer) แล้วส่งต่อให้อีกเทรดหนึ่ง (Consumer) แต่จะต้องมีการรอจังหวะกัน (Inter-Thread
Synchronization) เช่น เทรดที่สร้างข้อมูลจะต้องรอให้อีกเทรดหนึ่งรับข้อมูลไปใช้ก่อนที่จะสร้างข้อมูลลำดับถัดไป มิ
เช่นนั้นก็อาจเกิดปัญหาได้เรียกว่า Race Condition
ดังนั้นในกรณีนี้ จึงจะใช้สิ่งที่เรียกว่า Mutex (Mutual Exclusive) ซึ่งทำหน้าที่เป็นเสมือนล็อคป้องกัน (Lock) เพื่อเข้า
ใช้งานทรัพยากรร่วม หรือเพื่อการจำกัดสิทธิ์การเข้าถึงทรัพยากรร่วมระหว่างเทรด ยกตัวอย่างเช่น ถ้ามีหลายเทรด และ
ต้องการให้ในช่วงเวลาใดเวลาหนึ่ง มีเพียงเทรดเดียวเท่านั้นที่เข้าใช้ทรัพยากรร่วมได้
ถ้าเทรดใดจะเข้าถึงตัวแปรที่แชร์ใช้งานร่วมกัน จะต้องพยายามใส่ล็อคเพื่อป้องกันมิให้เทรดอื่นเข้ามาใช้งานได้และเมื่อ
เสร็จแล้ว ก็ต้องปลดล็อคดังกล่าว
การทำงานของ Mutex มีลักษณะคล้ายกับเซมาฟอร์แบบไบนารี แต่ก็มีความแตกต่างกัน เช่น หลักการทำงานแบบมี
เจ้าของ (Ownership) และ Recursive Mutex
คำสั่งที่เกี่ยวกับการใช้งาน Mutex Lock (pthread_mutex_t) ที่สำคัญได้แก่
pthread_mutex_init(...) เพื่อสร้างและเริ่มต้นใช้งาน mutex
pthread_mutex_lock(...) เพื่อปิดล็อคของ mutex แต่ถ้าไม่ได้จะบล็อกการทำงานของเทรดไปจนกว่าจะปิดล็อค
ได้
pthread_mutex_trylock(...) เพื่อลองดูว่าสามารถปิดล็อคของ mutex ได้หรือไม่ ถ้าไม่ได้ก็จะไม่มีการบล็อกการ
ทำงานของเทรด
pthread_mutex_unlock(...) เพื่อปลดล็อคของ mutex
pthread_destroy(...) เลิกใช้mutex
ในตัวอย่างนี้ ตัวแปร message ซึ่งเป็นอาร์เรย์แบบ (char *) สำหรับเก็บข้อความ (String) จะถูกใช้ในการส่งข้อมูล
ระหว่างเทรดที่เป็น Producer และ Consumer ดังนั้นจึงถือว่าเป็นส่วนที่เรียกว่า "เขตวิกฤต" (Critical Section) ถ้าจะ
เข้าใช้งานตัวแปรนี้จะต้องมีการใช้Mutex Lock ก่อนทุกครั้ง และจะมีการสร้างข้อความทั้งหมด 10 ครั้ง
เทรดที่ทำหน้าที่เป็น Producer จะต้องตรวจสอบก่อนว่า ตัวแปร flag เป็น false หรือไม่ จึงจะเขียนข้อความใหม่ลงไป
และเทรดที่เป็น Consumer จะต้องตรวจสอบค่าของตัวแปรนี้เช่นกัน แต่เมื่อนำข้อความไปใช้แสดงผลแล้วเปลี่ยนค่า
ของตัวแปร flag เป็น true
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <string.h>
4
#include <stdbool.h>
5
#include <pthread.h>
6
7
// global variables
8
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
9
static char message[32]; // shared variable
10
static bool running = true;
11
static bool flag = false;
12
13
void *producer_func( void *arg ) {
14
	 int cnt = 1;
15
	 while (cnt <= 10) {
16
	 // blocking call
17
	 	 pthread_mutex_lock( &lock );
18
	 	 if (!flag) {
19
	 	 	 snprintf( message, sizeof(message),
20
	 	 	 "hello #%d", cnt++ );
21
	 	 	 printf( "Producer> %sn", message );
22
flag = true;
23
}
24
	 	 pthread_mutex_unlock( &lock );
25
	 	 usleep( 1000 + (rand() % 1000) );
26
	 }
27
	 return NULL;
28
}
29
30
void *consumer_func( void *arg ) {
31
	 while (running) {
32
	 // blocking call
33
	 	 pthread_mutex_lock( &lock );
34
	 	 if ( flag ) {
35
	 	 	 printf( "Consumer> %snn", message );
36
	 	 	 message[0] = '0'; // clear message string
37
flag = false;
38
	 	 	 usleep( (rand() % 1000)*1000 );
39
	 	 }
40
	 	 pthread_mutex_unlock( &lock );
41
	 	 usleep(1);
42
	 }
43
	 return NULL;
44
}
45
46
int main( int argc, char *argv[] ) {
47
	 int retval;
48
	 pthread_t producer_thread, consumer_thread;
49
	
50
	 // initialize the pseudorandom generator with a seed
51
	 srand( time(NULL) );
52
	
53
	 // clear message buffer
54
	 memset( message, 0, sizeof(message) );
55
56
	 // create a mutex lock
57
	 if ( pthread_mutex_init( &lock, NULL ) ) {
58
	 	 printf( "Mutex init failed!n" );
59
	 	 exit(1);
60
	 }
61
62
	 // create producer and consumer threads
63
	 retval = pthread_create(
64
&producer_thread, NULL,
65
producer_func, (void*) NULL );
66
	 if (retval) {
67
	 	 printf( "Create producer thread failed!n" );
68
	 	 exit(1);
69
	 }
70
	 retval = pthread_create(
71
&consumer_thread, NULL,
72
consumer_func, (void*) NULL );
73
	 if (retval) {
74
	 	 printf( "Create consumer thread failed!n" );
75
	 	 exit(1);
76
	 }
77
78
	 usleep( 10000 /*usec*/ ); // sleep for 10msec before proceeding
79
	
80
	 // wait for producer thread
81
	 pthread_join( producer_thread, NULL );
82
	 running = false; // change running flag to false
83
	 pthread_cancel( consumer_thread ); // cancel consumer thread
84
	 pthread_join( consumer_thread, NULL ); // wait for consumer thread
85
	 pthread_mutex_destroy( &lock ); // destroy mutex lock
86
	 printf( "Done...nn" );
87
	 return 0;
88
}
89
ในตัวอย่างนี้ เทรด Producer จะจบการทำงานเอง เมื่อได้สร้างข้อความตามจำนวนที่กำหนดไว้ครบแล้ว แต่สำหรับการ
ทำงานของเทรด Consumer จะมีการใช้คำสั่ง pthread_cancel(...) เพื่อขอให้จบการทำงานหลังจากนั้น
ถ้าจะลองเปลี่ยนมาใช้คำสั่ง pthread_mutex_trylock(...) สำหรับฟังก์ชันการทำงานของเทรด ก็มีตัวอย่างโค้ดดังนี้
void *producer_func( void *arg ) {
1
int cnt = 1;
2
while (cnt <= 10) {
3
// blocking call
4
while( pthread_mutex_trylock( &lock ) ) {
5
usleep( 1000 );
6
	 }
7
if (!flag) {
8
snprintf( message, sizeof(message),
9
"hello #%d", cnt++ );
10
printf( "Producer> %sn", message );
11
flag = true;
12
}
13
pthread_mutex_unlock( &lock );
14
usleep( 1000 + (rand() % 1000) );
15
}
16
return NULL;
17
}
18
19
void *consumer_func( void *arg ) {
20
while (running) {
21
// blocking call
22
while( pthread_mutex_trylock( &lock ) ) {
23
usleep( 1000 );
24
	 }
25
if ( flag ) {
26
printf( "Consumer> %snn", message );
27
message[0] = '0'; // clear message string
28
flag = false;
29
usleep( (rand() % 1000)*1000 );
30
}
31
pthread_mutex_unlock( &lock );
32
usleep(1);
33
}
34
return NULL;
35
}
36
ตัวอย่างที่ 4: การใช้ Condition Variable
ถ้าต้องการให้เทรดหยุดรอ (Waiting / Blocked) เหตุการณ์ตามเงื่อนไขที่กำหนดไว้จนกว่าเงื่อนไขจะเป็นจริง แล้วจึง
ให้ทำงานต่อไป ในสถานการณ์เช่นนี้ เราสามารถใช้ตัวแปรประเภทหนึ่งที่เรียกว่า "ตัวแปรเงื่อนไข" (Condition
Variable) และการทำให้เงื่อนไขเป็นจริงได้นั้น จะต้องมีการส่งสัญญาณ (Signaling) จากเทรดอื่นมา ตัวแปรเงื่อนไขจึง
เป็นอีกวิธีการหนึ่งในการสื่อสารกันระหว่างเทรด (Inter-Thread Communication)
ในไลบรารีของ POSIX Pthread ก็มีคำสั่งสำหรับการใช้งาน Condition Variable (ชนิดข้อมูล pthread_cond_t) โดย
จะต้องใช้ร่วมกับ Mutex (ชนิดข้อมูล pthread_mutex_t) และคำสั่งที่เกี่ยวข้องได้แก่
pthread_cond_init(...) สร้างและเริ่มต้นใช้งานตัวแปรเงื่อนไข
pthread_cond_wait(...) รอจนกว่าตัวแปรเงื่อนไขจะเป็นจริง
pthread_cond_timedwait(...) รอจนกว่าตัวแปรเงื่อนไขจะเป็นจริง แต่มีระยะเวลาจำกัดในการรอ
pthread_cond_signal(...) ส่งสัญญาณไปยังเทรดที่รออยู่ เพื่อระบุว่าตัวแปรเงื่อนไขเป็นจริง และเทรดที่รอ
เงื่อนไขอยู่จะทำงานต่อได้
pthread_cond_broadcast(...) ส่งสัญญาณไปยังทุกเทรดที่รออยู่ เพื่อระบุว่าตัวแปรเงื่อนไขเป็นจริง
pthread_cond_destroy(...) เลิกใช้ตัวแปรแบบเงื่อนไข
ตัวอย่างนี้สาธิตการสร้างเทรดขึ้นมาใหม่ และให้เทรดดังกล่าวรอให้มีการส่งสัญญาณมาจากเทรดหลักในฟังก์ชัน
main() และจะเพิ่มค่าตัวนับของตัวแปรภายในและแสดงข้อความตัวเลขดังกล่าว การส่งสัญญาณสำหรับตัวแปรเงื่อนไข
ในตัวอย่างจะเกิดขึ้นทั้งหมด 10 ครั้ง
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <pthread.h>
4
5
// global variables
6
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
7
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
8
9
void *thread_func( void *arg ) {
10
int ticks = 0;
11
while (1) {
12
pthread_mutex_lock( &mutex );
13
// wait for signal
14
if ( !pthread_cond_wait( &cond, &mutex ) ) {
15
printf( "ticks: %dn", ++ticks );
16
}
17
pthread_mutex_unlock( &mutex );
18
}
19
printf( "Thread exited..n" );
20
return NULL;
21
}
22
23
int main( int argc, char *argv[] ) {
24
int retval;
25
pthread_t thread;
26
27
// create a mutex lock
28
if ( pthread_mutex_init( &mutex, NULL ) ) {
29
printf( "Mutex init failed!n" );
30
exit(1);
31
}
32
// create a condition variable
33
if ( pthread_cond_init( &cond, NULL ) ) {
34
printf( "Cond init failed!n" );
35
exit(1);
36
}
37
// create a thread
38
retval = pthread_create(
39
&thread, NULL, thread_func, (void*) NULL );
40
if (retval) {
41
printf( "Create a new thread failed!n" );
42
exit(1);
43
}
44
45
usleep(10);
46
for ( int i=0; i < 10; i++ ) {
47
pthread_mutex_lock( &mutex );
48
pthread_cond_signal( &cond );
49
pthread_mutex_unlock( &mutex );
50
sleep(1);
51
}
52
pthread_cancel( thread );
53
pthread_join( thread, NULL );
54
pthread_cond_destroy( &cond );
55
pthread_mutex_destroy( &mutex ); // destroy mutex lock
56
printf( "Done...nn" );
57
return 0;
58
}
59
แต่ถ้าจะลองเปลี่ยนมาใช้คำสั่ง pthread_cond_timewait(...) เพื่อให้เทรดสามารถรอเงื่อนไข แต่มีระยะเวลาจำกัด
(Timeout) เช่น 1 วินาที แล้วจึงตรวจสอบซ้ำ ก็มีตัวอย่างโค้ดดังนี้
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <time.h>
4
#include <pthread.h>
5
6
// global variables
7
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
8
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
9
static struct timespec ts;
10
11
void *thread_func( void *arg ) {
12
int ticks = 0;
13
int retval;
14
time_t now;
15
while (1) {
16
pthread_mutex_lock( &mutex );
17
// wait for signal with timeout (1 second)
18
time( &now ); // get current timestamp
19
ts.tv_sec = now + 1; // plus 1 second
20
ts.tv_nsec = 0;
21
retval = pthread_cond_timedwait( &cond, &mutex, &ts );
22
if ( retval == 0 ) {
23
printf( "ticks: %dn", ++ticks );
24
} else { // timeout
25
time( &now );
26
printf( "timeout at %sn", ctime(&now) );
27
}
28
pthread_mutex_unlock( &mutex );
29
}
30
printf( "Thread exited..n" );
31
return NULL;
32
}
33
34
int main( int argc, char *argv[] ) {
35
int retval;
36
pthread_t thread;
37
38
srand( time(NULL) );
39
40
// create a mutex lock
41
if ( pthread_mutex_init( &mutex, NULL ) ) {
42
printf( "Mutex init failed!n" );
43
exit(1);
44
}
45
// create a condition variable
46
if ( pthread_cond_init( &cond, NULL ) ) {
47
printf( "Cond init failed!n" );
48
exit(1);
49
}
50
// create a thread
51
retval = pthread_create(
52
&thread, NULL, thread_func, (void*) NULL );
53
if (retval) {
54
printf( "Create a new thread failed!n" );
55
exit(1);
56
}
57
58
usleep(10);
59
for ( int i=0; i < 10; i++ ) {
60
pthread_mutex_lock( &mutex );
61
pthread_cond_signal( &cond );
62
pthread_mutex_unlock( &mutex );
63
usleep( (rand()%2000)*1000 );
64
}
65
pthread_cancel( thread );
66
pthread_join( thread, NULL );
67
pthread_cond_destroy( &cond ); // destroy condition variable
68
pthread_mutex_destroy( &mutex ); // destroy mutex lock
69
printf( "Done...nn" );
70
return 0;
71
}
72
ถ้าจะลองใช้ตัวแปรเงื่อนไขกับตัวอย่างการสื่อสารกันระหว่างเทรดแบบ Producer-Consumer ก็มีตัวอย่างโค้ดดังนี้
การใช้ตัวแปรเงื่อนไขโดยแยกสำหรับเทรด Producer และเทรด Consumer แต่มีการใช้Mutex ร่วมกัน
เทรด Producer จะต้องรอตัวแปรเงื่อนไข cond_p และมีการส่งสัญญาณจากเทรด Consumer
เทรด Consumer จะต้องรอตัวแปรเงื่อนไข cond_c และมีการส่งสัญญาณจากเทรด Producer
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <string.h>
4
#include <stdbool.h>
5
#include <time.h>
6
#include <pthread.h>
7
8
// global variables
9
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
10
static pthread_cond_t cond_c = PTHREAD_COND_INITIALIZER;
11
static pthread_cond_t cond_p = PTHREAD_COND_INITIALIZER;
12
static char message[32] = {'0'};
13
static bool flag = false;
14
15
void *producer_thread_func( void *arg ) {
16
int ticks = 0;
17
while (1) {
18
pthread_mutex_lock( &mutex );
19
while ( flag ) { // wait for signal from consumer thread
20
pthread_cond_wait( &cond_p, &mutex );
21
}
22
flag = true;
23
snprintf( message, sizeof(message), "test %d", ++ticks );
24
printf( "Producer> %sn", message );
25
// send signal to consumer thread
26
pthread_cond_signal( &cond_c );
27
pthread_mutex_unlock( &mutex );
28
usleep( (1+rand()%1000)*1000 );
29
if (ticks >= 10) break;
30
}
31
printf( "Producer thread exited..n" );
32
return NULL;
33
}
34
35
void *consumer_thread_func( void *arg ) {
36
while (1) {
37
pthread_mutex_lock( &mutex );
38
while ( !flag ) { // wait for signal from consumer thread
39
pthread_cond_wait( &cond_c, &mutex );
40
}
41
flag = false;
42
printf( "Consumer> %snn", message );
43
message[0] = '0';
44
// send signal to producer thread
45
pthread_cond_signal( &cond_p );
46
pthread_mutex_unlock( &mutex );
47
usleep( (1+rand()%1000)*1000 );
48
}
49
printf( "Consumer thread exited..n" );
50
return NULL;
51
}
52
53
int main( int argc, char *argv[] ) {
54
int retval;
55
pthread_t thread_p, thread_c;
56
57
srand( time(NULL) );
58
59
// create a mutex lock
60
if ( pthread_mutex_init( &mutex, NULL ) ) {
61
printf( "Mutex init failed!n" );
62
exit(1);
63
}
64
// create condition variables
65
if ( pthread_cond_init( &cond_p, NULL ) ) {
66
printf( "Cond init failed!n" );
67
exit(1);
68
}
69
if ( pthread_cond_init( &cond_c, NULL ) ) {
70
printf( "Cond init failed!n" );
71
exit(1);
72
}
73
// create Producer thread
74
retval = pthread_create(
75
&thread_p, NULL,
76
producer_thread_func,
77
(void*) NULL );
78
if (retval) {
79
printf( "Create producer thread failed!n" );
80
exit(1);
81
}
82
// create Consumer thread
83
retval = pthread_create(
84
&thread_c, NULL,
85
consumer_thread_func,
86
(void*) NULL );
87
if (retval) {
88
printf( "Create consumer thread failed!n" );
89
exit(1);
90
}
91
92
pthread_join( thread_p, NULL );
93
pthread_cancel( thread_c );
94
pthread_join( thread_c, NULL );
95
pthread_cond_destroy( &cond_p );
96
pthread_cond_destroy( &cond_c );
97
pthread_mutex_destroy( &mutex );
98
printf( "Done...nn" );
99
return 0;
100
}
101
ถ้าจะลองใช้คำสั่ง pthread_cond_broadcast() เพื่อส่งสัญญาณไปยังทุกเทรดที่มีหลายเทรด (ตามจำนวนที่กำหนด
โดย NUM_THREADS) และรอตัวแปรเงื่อนไขเดียวกัน ก็มีตัวอย่างดังนี้
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <stdbool.h>
4
#include <time.h>
5
#include <pthread.h>
6
7
#define NUM_THREADS (4)
8
9
// global variables
10
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
11
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
12
static bool is_running = true;
13
14
void *thread_func( void *arg ) {
15
long id = (long)arg;
16
int ticks = 0;
17
while (is_running) {
18
pthread_mutex_lock( &mutex );
19
pthread_cond_wait( &cond, &mutex );
20
printf( "Thread: #%ld, ticks=%dn", id, ++ticks );
21
pthread_mutex_unlock( &mutex );
22
usleep(10);
23
}
24
sleep(1);
25
printf( "nThread #%ld exited..n", id );
26
return NULL;
27
}
28
29
int main( int argc, char *argv[] ) {
30
int retval;
31
pthread_t threads[ NUM_THREADS ];
32
33
srand( time(NULL) );
34
35
// create a mutex lock
36
if ( pthread_mutex_init( &mutex, NULL ) ) {
37
printf( "Mutex init failed!n" );
38
exit(1);
39
}
40
// create a condition variable
41
if ( pthread_cond_init( &cond, NULL ) ) {
42
printf( "Cond init failed!n" );
43
exit(1);
44
}
45
// create threads
46
for ( int i=0; i < NUM_THREADS; i++ ) {
47
retval = pthread_create(
48
&threads[i], NULL, thread_func,
49
(void*) (long)(i+1) );
50
if (retval) {
51
printf( "Create a new thread failed!.n" );
52
exit(1);
53
}
54
}
55
56
usleep( 1000 );
57
int N = 10;
58
for ( int i=0; i < N; i++ ) {
59
pthread_mutex_lock( &mutex );
60
is_running = (i==N-1) ? false : true;
61
pthread_cond_broadcast( &cond );
62
pthread_mutex_unlock( &mutex );
63
printf("-------------------------n");
64
usleep( (rand()%1000)*1000 );
65
}
66
67
sleep(1);
68
for (int i=0; i < NUM_THREADS; i++) {
69
pthread_join( threads[i], NULL );
70
}
71
pthread_cond_destroy( &cond ); // destroy condition variable
72
pthread_mutex_destroy( &mutex ); // destroy mutex lock
73
printf( "Done...nn" );
74
return 0;
75
}
76
ตัวอย่างที่ 5: การใช้ Barrier
ในกรณีที่มีการสร้างเทรดขึ้นมาทำงานไปพร้อม ๆ กัน แต่จะต้องมีการรอจังหวะให้ทุกเทรดทำงานในแต่ละขั้นตอนย่อย
เสร็จครบทุกเทรด แล้วจึงอนุญาตให้ทุกเทรดสามาารถทำงานในขั้นตอนต่อไปได้เราจะใช้สิ่งที่เรียกว่า Barrier เปรียบ
เสมือนแผงกั้นให้ทุกเทรดมาถึงจนครบตามจำนวนที่กำหนดไว้จึงเปิดขึ้นให้ทำงานต่อไปได้
การใช้POSIX Pthreads ก็มีชนิดของข้อมูล แต่คำสั่งที่เกี่ยวข้องดังนี้
pthread_barrier_init() สร้าง Barrier สำหรับใช้งาน
pthread_barrier_wait() รอจนกว่าจะได้รับการแจ้งให้ผ่านไปได้
pthread_barrier_destroy() เลิกใช้Barrier
ตัวอย่างโค้ดสาธิตการใช้Barrier มีดังนี้ โดยมีการสร้างเทรดขึ้นมาตามจำนวนที่กำหนด (NUM_THREADS) และทุก
เทรดที่สร้างขึ้นมานั้นรวมถึงเทรดหลักจะต้องรอจังหวะกันโดยใช้คำสั่ง pthread_barrier_wait()
#include <stdio.h>
1
#include <stdlib.h>
2
#include <unistd.h>
3
#include <stdbool.h>
4
#include <time.h>
5
#include <pthread.h>
6
7
#define NUM_THREADS (4)
8
9
// global variables
10
static pthread_barrier_t barrier;
11
static bool is_running = true;
12
13
void *thread_func( void *arg ) {
14
long id = (long)arg;
15
int ticks = 0;
16
while (is_running) {
17
pthread_barrier_wait( &barrier );
18
printf( "Thread: #%ld, ticks=%dn", id, ++ticks );
19
usleep( (1+rand()%2000)*1000 );
20
}
21
sleep(1);
22
printf( "nThread #%ld exited..n", id );
23
return NULL;
24
}
25
26
int main( int argc, char *argv[] ) {
27
int retval;
28
pthread_t threads[ NUM_THREADS ];
29
30
srand( time(NULL) );
31
32
// create a barrier
33
if (pthread_barrier_init( &barrier, NULL, NUM_THREADS+1) ) {
34
printf( "Barrier init failed!n" );
35
exit(1);
36
}
37
// create threads
38
for (int i=0; i < NUM_THREADS; i++ ) {
39
retval = pthread_create(
40
&threads[i], NULL, thread_func,
41
(void*) (long)(i+1) );
42
if (retval) {
43
printf( "Create a new thread failed!n" );
44
exit(1);
45
}
46
}
47
48
for ( int i=0; i < 10; i++ ) {
49
pthread_barrier_wait( &barrier );
50
}
51
52
is_running = false;
53
for (int i=0; i < NUM_THREADS; i++) {
54
pthread_join( threads[i], NULL );
55
}
56
pthread_barrier_destroy( &barrier ); // destroy barrier
57
printf( "Done...nn" );
58
return 0;
59
}
60
ตัวอย่างโค้ด C++ สาธิตการใช้งาน std::thread
ตัวอย่างที่ 1: Thread Creation
โค้ดในตัวอย่างนี้สาธิตการสร้างเทรด (std::thread) จำนวน 2 เทรด (thread1 และ thread2) และเมื่อเทรดทำงาน จะ
ต้องมีการล็อคการใช้งานของ Mutex (std::mutex) ให้ได้ก่อนแล้วจึงจะแสดงหมายเลขของเทรดเป็นข้อความ จากนั้น
จะมีการรอเวลาซึ่งมีระยะเวลาตามตัวเลขสุ่มในช่วง 1000 ถึง 2000 (มิลลิวินาที) แล้วจึงปลดล็อค Mutex
// g++ -std=c++14 main.cc -o main -lpthread
1
2
#include <iostream> // for std::cout
3
#include <thread> // for std:thread
4
#include <mutex> // for std::mutex
5
#include <cstdlib> // for std::srand(), std::rand()
6
#include <vector> // for std::vector()
7
8
using namespace std::chrono;
9
using namespace std::literals::chrono_literals;
10
11
// global variable
12
std::mutex output_lock;
13
14
static void thread_func( long arg ) {
15
	 output_lock.lock();
16
	 std::cout << "Thread #" << arg << "n";
17
	 // generate a random integer value between 1000..2000
18
	 long msec = 1000 + (std::rand() % 1000);
19
	 std::this_thread::sleep_for(
20
std::chrono::milliseconds(msec) );
21
	 output_lock.unlock();
22
}
23
24
int main() {
25
	 std::srand( std::time(nullptr) ); // set seed value
26
27
	 // create two threads
28
	 std::thread thread1( thread_func, 1 );
29
	 std::thread thread2( thread_func, 2 );
30
	 // sleep for 1 second
31
std::this_thread::sleep_for( 1s );
32
	 // wait for threads 1 and 2
33
	 if( thread1.joinable() ) {
34
	 	 thread1.join();
35
	 }
36
	 if( thread2.joinable() ) {
37
	 	 thread2.join();
38
	 }
39
	 return 0;
40
}
41
ตัวอย่างที่ 2: Mutex
การเขียนโค้ดภาษา C++ นั้น มีความหลากหลายมากกว่าภาษา C เนื่องจากรองรับการเขียนโปรแกรมแบบเชิงวัตถุ และมี
การสร้างคลาส C++ ไว้สำหรับใช้งานได้เป็นมาตรฐาน ตัวอย่างโค้ดต่อไปนี้ ให้ผลการทำงานเหมือนโค้ดตัวอย่างแรก
แต่มีการเพิ่มจำนวนเทรดให้มีมากกว่า 2 ได้และมีรูปแบบการเขียนที่แตกต่างกันออกไป เช่น มีการใช้เวกเตอร์
(std::vector) สำหรับอ้างอิงออปเจกต์ของเทรด (std::thread) ที่ได้มีการสร้างขึ้นมา และมีการสร้างฟังก์ชันสำหรับ
เทรดแต่ละเทรด ให้อยู่ในรูปแบบที่เรียกว่า Anonymous Function
// g++ -std=c++14 main.cc -o main -lpthread
1
2
#include <iostream> // for std::cout
3
#include <thread> // for std:thread
4
#include <mutex> // for std::mutex
5
#include <cstdlib> // for std::srand(), std::rand()
6
#include <vector> // for std::vector
7
#include <algorithm> // for std::for_each()
8
9
using namespace std::chrono;
10
using namespace std::literals::chrono_literals;
11
12
// global variable
13
std::mutex output_lock;
14
15
int main() {
16
std::srand( std::time(nullptr) );
17
18
const int NUM_THREADS = 10;
19
// use a vector container stores threads
20
std::vector<std::thread> workers; // worker threads
21
for ( int i=0; i < NUM_THREADS; i++ ) {
22
// create a thread with an anonymous function
23
// and add it to the vector container
24
workers.push_back( std::thread ( [](long arg){
25
output_lock.lock();
26
std::cout << "Thread #" << arg << "n";
27
// generate a random integer value between 1000..2000
28
long msec = 1000 + (std::rand() % 1000);
29
std::this_thread::sleep_for(
30
std::chrono::milliseconds(msec) );
31
output_lock.unlock();
32
}, i+1) );
33
}
34
35
// sleep for 1 second
36
std::this_thread::sleep_for(1s);
37
38
// wait for worker threads
39
std::for_each(workers.begin(),workers.end(),[](std::thread& t) {
40
if (t.joinable()) {
41
t.join();
42
}
43
});
44
return 0;
45
}
46
47
ตัวอย่างที่ 3: Condition Variable
โค้ดในตัวอย่างนี้สาธิตการใช้งาน Mutex (std::mutex) ร่วมกับตัวแปรเงื่อนไข (std::condition_variable) โดยให้
เทรดหลักของฟังก์ชัน main() คอยส่งสัญญาณโดยใช้คำสั่ง notify_onice(...) ไปยังอีกเทรดหนึ่งที่ถูกสร้างขึ้นใหม่
(เป็นการส่งสัญญาณรอจังหวะในทิศทางเดียว) และให้เทรดนั้นรอสัญญาณแบบไม่จำกัดเวลาโดยใช้คำสั่ง wait(...)
สำหรับตัวแปรเงื่อนไข ในการทำงานแต่ละรอบของลูป while
// g++ -std=c++14 main.cc -o main -lpthread
1
2
#include <iostream> // for std::cout
3
#include <thread> // for std:thread
4
#include <mutex> // for std::mutex
5
#include <condition_variable> // for std::condition_variable
6
7
using namespace std::chrono;
8
using namespace std::literals::chrono_literals;
9
10
// global variable
11
std::mutex mtx;
12
std::condition_variable cv;
13
bool running = true;
14
bool ready = false;
15
16
void thread_func() {
17
int ticks = 0;
18
while (running) {
19
std::unique_lock<std::mutex> lock(mtx);
20
while (!ready) {
21
cv.wait( lock );
22
}
23
ready = false;
24
if (running) {
25
std::cout << "Thread: ticks=" << (++ticks) << "n";
26
}
27
// The mutex lock is automatically released after each while loop.
28
}
29
}
30
31
int main() {
32
// create a thread
33
std::thread t( thread_func );
34
35
for ( int i=0; i < 10; i++ ) {
36
mtx.lock();
37
ready = true;
38
cv.notify_one();
39
mtx.unlock();
40
std::this_thread::sleep_for(500ms);
41
}
42
43
mtx.lock();
44
ready = true;
45
running = false;
46
cv.notify_one();
47
mtx.unlock();
48
if ( t.joinable() ) {
49
t.join();
50
}
51
return 0;
52
}
53
ถ้าจะเปลี่ยนมาใช้คำสั่ง wait_for(...) ซึ่งเป็นการรอที่มีระยะเวลาจำกัด (Timeout) สำหรับตัวแปรเงื่อนไข ก็มีตัวอย่าง
โค้ดดังนี้
// g++ -std=c++14 main.cc -o main -lpthread
1
2
#include <iostream> // for std::cout
3
#include <thread> // for std:thread
4
#include <mutex> // for std::mutex
5
#include <condition_variable> // for std::condition_variable
6
7
using namespace std::chrono;
8
using namespace std::literals::chrono_literals;
9
10
// global variable
11
std::mutex mtx;
12
std::condition_variable cv;
13
bool running = true;
14
bool ready = false;
15
16
void thread_func() {
17
int ticks = 0;
18
while (running) {
19
std::unique_lock<std::mutex> lock(mtx);
20
if ( cv.wait_for(lock,milliseconds(10))!=std::cv_status::timeout ){
21
if (ready) {
22
std::cout << "Thread: ticks=" << (++ticks) << "n";
23
ready = false;
24
}
25
}
26
// The mutex lock is automatically released after each while loop.
27
}
28
}
29
30
int main() {
31
// create a thread
32
std::thread t( thread_func );
33
34
for ( int i=0; i < 10; i++ ) {
35
mtx.lock();
36
ready = true;
37
cv.notify_one();
38
mtx.unlock();
39
std::this_thread::sleep_for(500ms);
40
}
41
42
mtx.lock();
43
ready = true;
44
running = false;
45
cv.notify_one();
46
mtx.unlock();
47
if ( t.joinable() ) {
48
t.join();
49
}
50
return 0;
51
}
52
ปรับปรุงแก้ไขล่าสุด: 2021-08-29

More Related Content

What's hot

รูปเล่มวิชาโครงงาน
รูปเล่มวิชาโครงงานรูปเล่มวิชาโครงงาน
รูปเล่มวิชาโครงงาน
AjBenny Pong
 
แผนการตลาด
แผนการตลาดแผนการตลาด
แผนการตลาด
Chotiros Duangpien
 
กิจกรรมที่7โครงการแต้มสีเติมฝัน
กิจกรรมที่7โครงการแต้มสีเติมฝันกิจกรรมที่7โครงการแต้มสีเติมฝัน
กิจกรรมที่7โครงการแต้มสีเติมฝันPrawwe Papasson
 
A msci60 key
A msci60 keyA msci60 key
A msci60 key
Wichai Likitponrak
 
ใบงานเพาเวอร์พอยต์
ใบงานเพาเวอร์พอยต์ใบงานเพาเวอร์พอยต์
ใบงานเพาเวอร์พอยต์poomarin
 
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรม
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรมแบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรม
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรมNutsara Mukda
 
Inthawong
InthawongInthawong
แบบประเมิน ความพึงพอใจ
แบบประเมิน ความพึงพอใจแบบประเมิน ความพึงพอใจ
แบบประเมิน ความพึงพอใจ
Pawit Chamruang
 
ฝึกคิดเลขเร็ว ป4(2)
ฝึกคิดเลขเร็ว   ป4(2)ฝึกคิดเลขเร็ว   ป4(2)
ฝึกคิดเลขเร็ว ป4(2)
ทับทิม เจริญตา
 
วิทย์ ป.2
วิทย์ ป.2วิทย์ ป.2
ตัวอย่างการเขียนโครงการหลัก
ตัวอย่างการเขียนโครงการหลักตัวอย่างการเขียนโครงการหลัก
ตัวอย่างการเขียนโครงการหลักAlongkorn WP
 
142968777910465
142968777910465142968777910465
142968777910465
YingZaa TK
 
แบบฟอร์มหนังสือภายนอก
แบบฟอร์มหนังสือภายนอกแบบฟอร์มหนังสือภายนอก
แบบฟอร์มหนังสือภายนอก
justymew
 
สารบัญภาษาไทย
สารบัญภาษาไทยสารบัญภาษาไทย
สารบัญภาษาไทยPoppy Nana
 
ใบงานส่วนประกอบคอมพิวเตอร์
ใบงานส่วนประกอบคอมพิวเตอร์ใบงานส่วนประกอบคอมพิวเตอร์
ใบงานส่วนประกอบคอมพิวเตอร์
bimteach
 
กระดาษเส้น
กระดาษเส้นกระดาษเส้น
กระดาษเส้นTik Msr
 

What's hot (20)

โครงการ เยี่ยมบ้าน
โครงการ     เยี่ยมบ้านโครงการ     เยี่ยมบ้าน
โครงการ เยี่ยมบ้าน
 
รูปเล่มวิชาโครงงาน
รูปเล่มวิชาโครงงานรูปเล่มวิชาโครงงาน
รูปเล่มวิชาโครงงาน
 
ใบลา
ใบลาใบลา
ใบลา
 
แผนการตลาด
แผนการตลาดแผนการตลาด
แผนการตลาด
 
กิจกรรมที่7โครงการแต้มสีเติมฝัน
กิจกรรมที่7โครงการแต้มสีเติมฝันกิจกรรมที่7โครงการแต้มสีเติมฝัน
กิจกรรมที่7โครงการแต้มสีเติมฝัน
 
A msci60 key
A msci60 keyA msci60 key
A msci60 key
 
ใบงานเพาเวอร์พอยต์
ใบงานเพาเวอร์พอยต์ใบงานเพาเวอร์พอยต์
ใบงานเพาเวอร์พอยต์
 
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรม
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรมแบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรม
แบบสังเกตพฤติกรรมความกระตือรือร้นในการเข้าร่วมกิจกรรม
 
Inthawong
InthawongInthawong
Inthawong
 
แบบประเมิน ความพึงพอใจ
แบบประเมิน ความพึงพอใจแบบประเมิน ความพึงพอใจ
แบบประเมิน ความพึงพอใจ
 
ฝึกคิดเลขเร็ว ป4(2)
ฝึกคิดเลขเร็ว   ป4(2)ฝึกคิดเลขเร็ว   ป4(2)
ฝึกคิดเลขเร็ว ป4(2)
 
บันทึกข้อความซื้อ
บันทึกข้อความซื้อบันทึกข้อความซื้อ
บันทึกข้อความซื้อ
 
วิทย์ ป.2
วิทย์ ป.2วิทย์ ป.2
วิทย์ ป.2
 
ตัวอย่างการเขียนโครงการหลัก
ตัวอย่างการเขียนโครงการหลักตัวอย่างการเขียนโครงการหลัก
ตัวอย่างการเขียนโครงการหลัก
 
142968777910465
142968777910465142968777910465
142968777910465
 
แบบฟอร์มหนังสือภายนอก
แบบฟอร์มหนังสือภายนอกแบบฟอร์มหนังสือภายนอก
แบบฟอร์มหนังสือภายนอก
 
สารบัญภาษาไทย
สารบัญภาษาไทยสารบัญภาษาไทย
สารบัญภาษาไทย
 
คู่มือ ชรบ.
คู่มือ ชรบ.คู่มือ ชรบ.
คู่มือ ชรบ.
 
ใบงานส่วนประกอบคอมพิวเตอร์
ใบงานส่วนประกอบคอมพิวเตอร์ใบงานส่วนประกอบคอมพิวเตอร์
ใบงานส่วนประกอบคอมพิวเตอร์
 
กระดาษเส้น
กระดาษเส้นกระดาษเส้น
กระดาษเส้น
 

Similar to Multi threaded programming in c and c++ 2021-09-03

Docker 101 for developer
Docker 101 for developerDocker 101 for developer
Docker 101 for developer
Arnon Kijlardphon
 
NETWORK SERVICEOPENSSH + NTP + SQUID
NETWORK SERVICEOPENSSH + NTP + SQUIDNETWORK SERVICEOPENSSH + NTP + SQUID
NETWORK SERVICEOPENSSH + NTP + SQUID
Ploynatcha Akkaraputtipat
 
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
หน่อย หน่อย
 
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
หน่อย หน่อย
 
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6Ploy StopDark
 
การเขียนโปรแกรมเพื่องานอาชีพ 6.
การเขียนโปรแกรมเพื่องานอาชีพ 6.การเขียนโปรแกรมเพื่องานอาชีพ 6.
การเขียนโปรแกรมเพื่องานอาชีพ 6.Ploy StopDark
 
Java Programming [12/12] : Thread
Java Programming [12/12] : ThreadJava Programming [12/12] : Thread
Java Programming [12/12] : Thread
IMC Institute
 
Lab Computer Programming 1
Lab Computer Programming 1Lab Computer Programming 1
Lab Computer Programming 1
Saranyu Srisrontong
 
ฟังก์ชันในภาษา
ฟังก์ชันในภาษาฟังก์ชันในภาษา
ฟังก์ชันในภาษา
Sedthawoot Pitapo
 
Loop Programming for Python Language programming
Loop Programming for Python Language programmingLoop Programming for Python Language programming
Loop Programming for Python Language programming
ssuser62cb36
 
พื้นฐานภาษาจาวา
พื้นฐานภาษาจาวาพื้นฐานภาษาจาวา
พื้นฐานภาษาจาวาAeew Autaporn
 
C lang
C langC lang
บทที่ 9 การพ้องรูป
บทที่ 9 การพ้องรูปบทที่ 9 การพ้องรูป
บทที่ 9 การพ้องรูปTheeravaj Tum
 

Similar to Multi threaded programming in c and c++ 2021-09-03 (20)

Docker 101 for developer
Docker 101 for developerDocker 101 for developer
Docker 101 for developer
 
Hotspotubuntu8
Hotspotubuntu8Hotspotubuntu8
Hotspotubuntu8
 
NETWORK SERVICEOPENSSH + NTP + SQUID
NETWORK SERVICEOPENSSH + NTP + SQUIDNETWORK SERVICEOPENSSH + NTP + SQUID
NETWORK SERVICEOPENSSH + NTP + SQUID
 
650 1
650 1650 1
650 1
 
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
น.ส.ศิริวิภา กาญจนาวิล-59170116-sec-1
 
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
นางสาว จรัญญา-กฤตย์ณัชช์-59170236-กลุ่ม-1
 
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6
โปรแกรมย่อยและฟังก์ชัน มาตรฐาน 6
 
การเขียนโปรแกรมเพื่องานอาชีพ 6.
การเขียนโปรแกรมเพื่องานอาชีพ 6.การเขียนโปรแกรมเพื่องานอาชีพ 6.
การเขียนโปรแกรมเพื่องานอาชีพ 6.
 
Java Programming [12/12] : Thread
Java Programming [12/12] : ThreadJava Programming [12/12] : Thread
Java Programming [12/12] : Thread
 
Coovaubuntu904
Coovaubuntu904Coovaubuntu904
Coovaubuntu904
 
Lab Computer Programming 1
Lab Computer Programming 1Lab Computer Programming 1
Lab Computer Programming 1
 
59170104
5917010459170104
59170104
 
ฟังก์ชันในภาษา
ฟังก์ชันในภาษาฟังก์ชันในภาษา
ฟังก์ชันในภาษา
 
Loop Programming for Python Language programming
Loop Programming for Python Language programmingLoop Programming for Python Language programming
Loop Programming for Python Language programming
 
พื้นฐานภาษาจาวา
พื้นฐานภาษาจาวาพื้นฐานภาษาจาวา
พื้นฐานภาษาจาวา
 
Chapter1
Chapter1Chapter1
Chapter1
 
02 basic
02 basic02 basic
02 basic
 
C lang
C langC lang
C lang
 
บทที่ 9 การพ้องรูป
บทที่ 9 การพ้องรูปบทที่ 9 การพ้องรูป
บทที่ 9 การพ้องรูป
 
Ch12 web-app-part2
Ch12 web-app-part2Ch12 web-app-part2
Ch12 web-app-part2
 

Multi threaded programming in c and c++ 2021-09-03

  • 1. Multi-Threaded Programming การเขียนโปรแกรมให้ทำงานแบบมัลติเทรดด้วยภาษา C/C++ การทำงานแบบมัลติเทรด แอปพลิเคชัน (Application) หรือโปรแกรมในระดับผู้ใช้(User-Level Program) ที่ทำงานภายใต้การจัดการของระบบ ปฏิบัติการ Linux จะเรียกว่า "โปรเซส" (Process) หลาย ๆ โปรเซสสามารถทำงานได้อิสระจากกัน ดังนั้นจึงเป็นการ ทำงานแบบหลายโปรเซส (Multi-Processing) โปรเซสก็สามารถสร้างโปรเซสอื่นอีกให้เริ่มต้นทำงานใหม่ได้เช่นกัน เป็นความสัมพันธ์ระหว่าง โปรเซสที่สร้าง (Parent Process) กับโปรเซสที่ถูกสร้าง (Child Process) และเรียกขั้นตอนนี้ว่า Process forking / spawning ในแต่ละโปรเซสก็สามารถแบ่งออกเป็นการทำงานย่อย ๆ เรียกว่า "เทรด" (Thread) มีหลายเทรดทำงานได้ไปได้พร้อม ๆ กัน (Multi-Threading) หรือเรียกว่า Concurrency ซึ่งจะมีประโยชน์และช่วยเพิ่มประสิทธิภาพในการทำงานของ ระบบเนื่องจากคอมพิวเตอร์มีตัวประมวลผลมากกว่าหนึ่งตัวได้(Multi-Processor / Multi-Core CPU) และเป็นการ เพิ่มระดับการทำงานแบบขนานกันในระดับฮาร์ดแวร์ (Parallel Processing) การทำงานแบบหลายเทรดนั้น สามารถใช้งานได้ทั้งแบบมีซีพียูเดียว (Single-CPU) โดยการแบ่งช่วงเวลากันทำงาน ระหว่างเทรด ให้ดูเสมือนว่าทำงานไปได้พร้อม ๆ กัน หรือใช้กับการทำงานของซีพียูที่มีหลายแกน (Multi-Core CPU) ก็ได้ แต่ถ้ามีการนำคอมพิวเตอร์มาใช้งานร่วมกันในระบบเครือข่ายที่เชื่อมต่อกันด้วยความเร็วสูง และเขียนโปรแกรมเพื่อให้ ทำงานร่วมกัน ให้เป็นส่วนหนึ่งของแอปพลิเคชันเดียวกัน ก็จะเรียกรูปแบบนี้ว่า การประมวลผลแบบกระจาย (Distributed Processing / Computing) เราสามารถเขียนโปรแกรมเพื่อให้มีมากกว่าหนึ่งเทรด และสามารถทำงานได้อิสระจากกัน แต่บางกรณีก็ต้องมีการสื่อสาร กันระหว่างเทรด หรือรอจังหวะการทำงานซึ่งกันและกัน (Inter-Thread Communication & Synchronization) ถ้าเปรียบเทียบการทำงานระหว่างโปรเซสกับเทรดโดยการจัดการของระบบปฏิบัติการ (OS) การสร้างเทรดขึ้นมาใหม่นั้น มีขั้นตอนและการใช้ทรัพยากรของระบบ (หรือเรียกว่า Overhead) น้อยกว่าการสร้างโปรเซส เช่น เทรดที่ถูกสร้างขึ้น ภายใต้โปรเซสเดียวกันจะแชร์การใช้หน่วยความจำเดียวกัน แต่ถ้าเป็นการสร้างโปรเซสใหม่ จะต้องมีการแยกหน่วย ความจำในการใช้งานระหว่างโปรเซสเหล่านั้น ในกรณีที่เขียนโปรแกรมด้วยภาษา C/C++ สำหรับ Linux ก็สามารรถใช้ไลบรารีชื่อว่า POSIX Pthreads (ตามมาตรฐาน (POSIX.1-2001 และ POSIX.1-2008) สำหรับการสร้างและจัดการทำงานของเทรดได้ตัวอย่างฟังก์ชันการทำงานที่ เกี่ยวกับเทรด เช่น การสร้างเทรดใหม่ (Thread creation) การจบการทำงานของเทรด (Thread termination) การทำงานแบบรอจังหวะกันระหว่างเทรด (Thread synchronization) การจัดลำดับการทำงานของเทรด (Thread scheduling) การจัดการข้อมูลของเทรดและปฏิสัมพันธ์กับโปรเซส (Thread data management and process interaction) ในกรณีที่เขียนโปรแกรมด้วยภาษาอื่น อย่างเช่น Java หรือ Python ก็มีไลบรารีให้ใช้งานสำหรับการทำงานแบบมัลติ เทรดเช่นกัน การทำงานของเทรดภายใต้โปรเซสเดียวกัน มีการแชร์หรือใช้ทรัพยากรร่วมกันระหว่างเทรด แต่ก็มีสิ่งที่แต่ละเทรดนั้น ใช้งานแยกกัน เช่น
  • 2. Thread ID เป็นหมายเลขที่แตกต่างกันและใช้ระบุเทรดแต่ละเทรด Thread priority เป็นระดับความสำคัญของเทรดและใช้ในการจัดลำดับการทำงานของเทรด Set of registers, stack pointer (SP) เป็นหน่วยความจำที่ใช้เก็บค่าของรีจิสเตอร์ต่าง ๆ ของซีพียูและเกี่ยวข้อง กับการทำงานของเทรด เนื่องจากมีการแชร์การใช้งานซีพียูร่วมกัน ข้อมูลเหล่านี้จะถูกจัดเก็บลงในหน่วยความจำที่ เรียกว่า Stack ของแต่ละเทรด ดังนั้นจึงต้องมีตัวระบุหรือชี้ตำแหน่งบนสุดของ Stack ดังกล่าว Stack for local variables, return addresses เป็นหน่วยความจำแบบ Stack ที่ใช้เก็บข้อมูลในขณะที่เทรด กำลังทำงาน เช่น เก็บค่าตัวแปรภายในเมื่อมีการเรียกใช้ฟังก์ชันและจบการทำงานของฟังก์ชัน เป็นต้น เนื่องจากในการทำงานแบบมัลติเทรด จะต้องมีการแบ่งเวลาหรือสลับเวลาในการทำงานกันระหว่างเทรด ดังนั้นจึงต้องมี การบันทึกสถานะการทำงานของเทรดและเรียกคืนกลับมาทำงานต่อ การสลับการทำงานของเทรด ก็เรียกว่า Thread Context Switching ถ้าจะลองเขียนโค้ดโดยใช้ภาษา C++ ก็มีความแตกต่างจากโค้ดภาษา C ออกไปบ้าง แต่ก็สามารถใช้คำสั่งหรือฟังก์ชัน ของไลบรารี POSIX Pthreads ได้เช่นกัน หรือจะใช้คลาส std::thread (สำหรับเวอร์ชัน C++11 C++14 และ C++17) นอกจากนั้นแล้วถ้าลองสืบค้นในอินเทอร์เน็ต จะพบว่า มีนักพัฒนาซอฟต์แวร์ได้สร้างคลาส C++ ทำหน้าที่เป็นตัวครอบ การใช้งานคำสั่งต่าง ๆ ของ POSIX Pthreads ไว้เป็นตัวอย่างหรือนำไปทดลองใช้งานได้โดยแชร์โค้ดไว้ใน Github เป็นต้น ตัวอย่างโค้ด C สาธิตการใช้งาน Pthreads เนื้อหาในส่วนนี้นำเสนอตัวอย่างการเขียนโค้ดภาษา C โดยใช้ไลบรารี POSIX Pthreads สาธิตการทำงานแบบมัลติ เทรดในรูปแบบต่าง ๆ ตัวอย่างที่ 1: Thread Creation โค้ดตัวอย่างแรกนี้ สาธิตการเขียนโปรแกรม เพื่อสร้างเทรดจำนวน 2 เทรด (Thread 1 และ Thread 2) โดยใช้คำสั่ง pthread_create(...) ซึ่งจะให้ค่ากลับคืนเป็น 0 ถ้าสร้างเทรดใหม่ได้สำเร็จ ในขั้นตอนการทำงานของเทรด จะต้องมีการสร้างฟังก์ชันตามรูปแบบที่กำหนดไว้โดย POSIX Pthreads ดังนี้ int pthread_create( pthread_t *thread, 1 const pthread_attr_t *attr, 2 void *(*start_routine)(void*), 3 void *arg ); 4 ในตัวอย่างนี้ใช้ชื่อฟังก์ชัน thread_entry_func(...) สำหรับ Thread Start Routine และเป็นตัวกำหนดฟังก์ชันการ ทำงานของเทรด ทั้งสองเทรดในตัวอย่างนี้ มีการใช้ฟังก์ชันดังกล่าวร่วมกัน และเมื่อสร้างเทรดขึ้นมาแล้ว จะใช้ตัวแปรที่ มีชนิดข้อมูลเป็น pthread_t เป็นตัวอ้างอิงแต่ละเทรด ในตัวอย่างนี้ฟังก์ชันนี้รับค่าอาร์กิวเมนต์มาเป็นพอยน์เตอร์แบบ (void *) แต่แปลงให้เป็นเลขจำนวนเต็มชนิด long เพื่อ ใช้จำแนกว่า เป็นเทรดหมายเลขใด เมื่อทำคำสั่งต่าง ๆ ของฟังก์ชันนี้ จะมีการรอเวลาให้ผ่านไปโดยใช้คำสั่ง sleep(...) ระยะเวลาในการรอหน่วยเป็นวินาที จะได้จากการใช้คำสั่ง rand(...) เพื่อสุ่มค่าตัวเลขจำนวนเต็ม โดยกำหนดให้มีค่าอยู ในช่วง 1..10 วินาที เมื่อรอเวลาตามที่กำหนดไว้แล้ว การทำงานของเทรดจึงจบลง (แต่ละเทรดจะใช้เวลารอไม่เหมือน กัน เพราะอาจสุ่มเลขได้ค่าต่างกัน ดังนั้นจึงจบการทำงานไม่พร้อมกัน) การทำงานของโปรแกรมนี้ เมื่อได้สร้างเทรดใหม่ทั้งสองแล้ว จากนั้นจะต้องรอให้เทรดทั้งสองจบการทำงานก่อน จึงจะ จบการทำงานของโปรแกรม การรอคอยให้เทรดของโปรเซสจบการทำงานนั้น จะต้องใช้คำสั่ง pthread_join(...) ดังนั้น จึงเป็นการเรียกฟังก์ชันแบบ Blocking Call หรือ รอไปแบบไม่มีเวลาจำกัดจนกว่าเงื่อนไขจะเป็นจริง
  • 3. // $ gcc -Wall main.c -o main -lpthread 1 2 #include <stdio.h> 3 #include <stdlib.h> // for srand(), rand() 4 #include <unistd.h> // for sleep(), usleep() 5 #include <pthread.h> // the header file for the pthread lib 6 7 void *thread_entry_func( void *arg ) { 8 long id = (long)arg; 9 printf( "Thread started: %ldn", id ); 10 // sleep for some seconds (randomized between 1..10) 11 sleep( 1 + (rand() % 10) ); 12 printf( "Thread finished: %ldn", id ); 13 return NULL; 14 } 15 16 int main( int argc, char *argv[] ) { 17 int retval; 18 pthread_t thread1, thread2; 19 20 // initialize the pseudorandom generator with a seed 21 srand( time(NULL) ); 22 23 // create Thread 1 24 retval = pthread_create( 25 &thread1 /*used to identify thread*/, 26 NULL /*default attributes*/, 27 thread_entry_func /*start routine*/, 28 (void*) 1 /*thread argument*/ ); 29 printf( "Create thread 1: %sn", 30 retval ? "FAILED" : "OK" ); 31 32 // create Thread 2 33 retval = pthread_create( 34 &thread2 /*used to identify thread*/, 35 NULL /*default attributes*/, 36 thread_entry_func /*start routine*/, 37 (void*) 2 /*thread argument*/ ); 38 printf( "Create thread 2: %sn", 39 retval ? "FAILED" : "OK" ); 40 41 usleep( 10000 /*usec*/ ); // sleep for 10msec 42 43 // wait until thread 1 and 2 are finished. 44 printf( "Waiting for threads to be finishedn" ); 45 if (thread1) { 46 pthread_join( thread1, NULL ); // wait for thread 1 47 } 48 if (thread2) { 49 pthread_join( thread2, NULL ); // wait for thread 2 50 } 51 printf( "Done...nn" ); 52 return 0; 53 } 54 รูปต่อไปนี้แสดงตัวอย่างการทำคำสั่งแบบ Command line เพื่อคอมไพล์โค้ดชื่อ main.c ที่มีโค้ดในตัวอย่างให้เป็น ไฟล์ executable ที่มีชื่อว่า main จากนั้นจึงรันโปรแกรมดังกล่าว
  • 4. ถ้าต้องการจะกำหนดคุณสมบัติของเทรด (pthread_attr_t) ที่จะถูกสร้างขึ้นใหม่โดยใช้คำสั่ง pthread_attr_init(...) ก็มี ตัวอย่างดังนี้ เช่น การกำหนดให้เทรดมีสถานะเป็น joinable และการเพิ่มขนาดของ Stack สำหรับการทำงานของเทรด เป็นต้น // $ gcc -Wall main.c -o main -lpthread 1 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> // for sleep(), usleep() 5 #include <pthread.h> // the header file for the pthread lib 6 7 void *thread_entry_func( void *arg ) { 8 printf( "Thread is active.n" ); 9 return NULL; 10 } 11 12 int main( int argc, char *argv[] ) { 13 int retval; 14 size_t stack_size; 15 pthread_attr_t attr; 16 pthread_t thread; 17 18 // create a thread attribute object 19 retval = pthread_attr_init(&attr); 20 if (retval) { 21 printf( "Error while creating thread attribute!n" ); 22 exit(1); 23 } 24 // use a joinable thread 25 pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); 26 // get the default stack size 27 pthread_attr_getstacksize( &attr, &stack_size ); 28 printf( "Default thread stack size: %ldn", stack_size ); 29 // set the stack size (use a larger value, say 4x) 30 pthread_attr_setstacksize( &attr, 4*stack_size ); 31 32 // create Thread 33 retval = pthread_create( 34 &thread, &attr, thread_entry_func, NULL ); 35 printf( "Create thread: %sn", 36 retval ? "FAILED" : "OK" ); 37 38 sleep(1); 39 // get the thread stack size 40 pthread_attr_getstacksize( &attr, &stack_size ); 41 printf( "Thread stack size: %ldn", stack_size ); 42 pthread_attr_destroy( &attr ); // destroy attribute 43 44 if (thread) { 45 pthread_join( thread, NULL ); // wait for thread 46
  • 5. } 47 printf( "Done...nn" ); 48 return 0; 49 } 50 ถัดไปเป็นการสร้างเทรดตามจำนวนที่กำหนดไว้โดย NUM_THREADS แต่มีลักษณะการทำงานเหมือนโค้ดในตัวอย่าง แรก #include <stdio.h> 1 #include <stdlib.h> // for srand(), rand() 2 #include <unistd.h> // for sleep(), usleep() 3 #include <pthread.h> // the header file for the pthread lib 4 5 #define NUM_THREADS (10) 6 7 void *thread_entry_func( void *arg ) { 8 long id = (long)arg; 9 printf( "Thread started: %ld (0x%08lX)n", id, pthread_self() ); 10 // sleep for some seconds (randomized between 1..10) 11 sleep( 1 + (rand() % 10) ); 12 printf( "Thread finished: %ldn", id ); 13 return NULL; 14 } 15 16 int main( int argc, char *argv[] ) { 17 int retval; 18 pthread_t threads[ NUM_THREADS ]; 19 20 // initialize the pseudorandom generator with a seed 21 srand( time(NULL) ); 22 23 // create a number of threads 24 for ( int i=0; i < NUM_THREADS; i++ ) { 25 long id = (i+1); // used as thread argument 26 retval = pthread_create( 27 &threads[i], NULL, 28 thread_entry_func, 29 (void*) id ); 30 printf( "Create thread %ld: %sn", id, 31 retval ? "FAILED" : "OK" ); 32 if ( retval ) { // thread creation error 33 printf( "Program exited...n" ); 34 exit(1); 35 } 36 } 37 38 usleep( 1000 /*usec*/ ); // sleep for 1msec before proceeding 39 40 // wait until all threads are finished. 41 printf( "Waiting for all threads to be finishedn" ); 42 for ( int i=0; i < NUM_THREADS; i++ ) { 43 pthread_join( threads[i], NULL ); // wait for thread 44 } 45 printf( "Done...nn" ); 46 return 0; 47 } 48 ตัวอย่างที่ 2: Binary Semaphore โค้ดในตัวอย่างนี้สาธิตการใช้งานสิ่งที่เรียกว่า "เซมาฟอร์แบบไบนารี" (Binary Semaphore) เพื่อใช้ส่งสัญญาณสื่อสาร กันระหว่างเทรด เช่น ให้เทรดหนึ่งรอสัญญาณจากอีกเทรดหนึ่ง หรือในกรณีที่จะต้องมีการใช้ทรัพยากรร่วมกัน คำสั่งที่เกี่ยวข้องกับการใช้งานเซมาฟอร์แบบไบนารีของ POSIX Pthreads มีดังนี้
  • 6. sem_init(...) ตั้งค่าเริ่มต้นของตัวนับและเปิดใช้งานเซมาฟอร์ sem_wait(..) รอการใช้งานเซมาฟอร์ ถ้าได้ให้ลดค่าลงจาก 1 เป็น 0 แต่ถ้ายังไม่ได้ให้รอไปก่อน sem_post(...) เพิ่มค่าเซมาฟอร์ให้เป็น 1 ทันทีโดยไม่ต้องรอ sem_destroy(...) เลิกใช้งานเซมาฟอร์ เซมาฟอร์แบบไบนารีจะมีค่าเริ่มต้นเป็น 1 เมื่อมีการเข้าใช้งานโดยเทรดก็จะลดค่าลงเป็น 0 โดยการทำคำสั่ง sem_wait(...) แต่ถ้ามีค่าเป็น 0 แล้วและมีเทรดใดต้องการใช้งาน จะต้องรอไปก่อน เมื่อใช้งานเซมาฟอร์แล้วก็ต้องเพิ่ม ค่าให้กลับคืนเป็น 1 ด้วยคำสั่ง sem_post(...) ข้อสังเกต: ถ้าให้ตัวนับของเซมาฟอร์มีค่าเริ่มต้นมากกว่า 1 เราจะเรียกว่า "เซมาฟอร์แบบนับ" (Counting Semaphore) // $ gcc -Wall main.c -o main -lpthread 1 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> // for sleep(), usleep() 5 #include <pthread.h> // the header file for the pthread lib 6 #include <semaphore.h> 7 8 #define NUM_THREADS (10) 9 10 // global variable 11 sem_t semaphore; 12 13 void *thread_entry_func( void *arg ) { 14 long id = (long)arg; 15 sleep(1); 16 sem_wait(&semaphore); // acquire the semaphore 17 printf( "Thread #%ld is active.n", id ); 18 sem_post(&semaphore); // release the semaphore 19 return NULL; 20 } 21 22 int main( int argc, char *argv[] ) { 23 int retval; 24 pthread_t threads[ NUM_THREADS ]; 25 26 // create a binary semaphore 27 sem_init( &semaphore, 0, 1 /*initial value*/ ); 28 29 // create Threads 30 for (int i=0; i < NUM_THREADS; i++) { 31 retval = pthread_create( 32 &threads[i], NULL, 33 thread_entry_func, 34 (void*)(i+1L) ); 35 printf( "Create thread: %sn", 36 retval ? "FAILED" : "OK" ); 37 if (retval) { 38 exit(1); 39 } 40 } 41 sleep(1); 42 43 for (int i=0; i < NUM_THREADS; i++) { 44 if (threads[i]) { 45 pthread_join( threads[i], NULL ); // wait for thread 46 } 47 } 48 sem_destroy( &semaphore ); // destroy semaphore 49 printf( "Done...nn" ); 50 return 0; 51 } 52
  • 7. ตัวอย่างที่ 3: Producer-Consumer การทำงานร่วมกันระหว่างเทรดตามรูปแบบที่เรียกว่า Producer-Consumer Pattern คือ ให้มีเทรดหนึ่งทำหน้าที่สร้าง ข้อมูล (Producer) แล้วส่งต่อให้อีกเทรดหนึ่ง (Consumer) แต่จะต้องมีการรอจังหวะกัน (Inter-Thread Synchronization) เช่น เทรดที่สร้างข้อมูลจะต้องรอให้อีกเทรดหนึ่งรับข้อมูลไปใช้ก่อนที่จะสร้างข้อมูลลำดับถัดไป มิ เช่นนั้นก็อาจเกิดปัญหาได้เรียกว่า Race Condition ดังนั้นในกรณีนี้ จึงจะใช้สิ่งที่เรียกว่า Mutex (Mutual Exclusive) ซึ่งทำหน้าที่เป็นเสมือนล็อคป้องกัน (Lock) เพื่อเข้า ใช้งานทรัพยากรร่วม หรือเพื่อการจำกัดสิทธิ์การเข้าถึงทรัพยากรร่วมระหว่างเทรด ยกตัวอย่างเช่น ถ้ามีหลายเทรด และ ต้องการให้ในช่วงเวลาใดเวลาหนึ่ง มีเพียงเทรดเดียวเท่านั้นที่เข้าใช้ทรัพยากรร่วมได้ ถ้าเทรดใดจะเข้าถึงตัวแปรที่แชร์ใช้งานร่วมกัน จะต้องพยายามใส่ล็อคเพื่อป้องกันมิให้เทรดอื่นเข้ามาใช้งานได้และเมื่อ เสร็จแล้ว ก็ต้องปลดล็อคดังกล่าว การทำงานของ Mutex มีลักษณะคล้ายกับเซมาฟอร์แบบไบนารี แต่ก็มีความแตกต่างกัน เช่น หลักการทำงานแบบมี เจ้าของ (Ownership) และ Recursive Mutex คำสั่งที่เกี่ยวกับการใช้งาน Mutex Lock (pthread_mutex_t) ที่สำคัญได้แก่ pthread_mutex_init(...) เพื่อสร้างและเริ่มต้นใช้งาน mutex pthread_mutex_lock(...) เพื่อปิดล็อคของ mutex แต่ถ้าไม่ได้จะบล็อกการทำงานของเทรดไปจนกว่าจะปิดล็อค ได้ pthread_mutex_trylock(...) เพื่อลองดูว่าสามารถปิดล็อคของ mutex ได้หรือไม่ ถ้าไม่ได้ก็จะไม่มีการบล็อกการ ทำงานของเทรด pthread_mutex_unlock(...) เพื่อปลดล็อคของ mutex pthread_destroy(...) เลิกใช้mutex ในตัวอย่างนี้ ตัวแปร message ซึ่งเป็นอาร์เรย์แบบ (char *) สำหรับเก็บข้อความ (String) จะถูกใช้ในการส่งข้อมูล ระหว่างเทรดที่เป็น Producer และ Consumer ดังนั้นจึงถือว่าเป็นส่วนที่เรียกว่า "เขตวิกฤต" (Critical Section) ถ้าจะ เข้าใช้งานตัวแปรนี้จะต้องมีการใช้Mutex Lock ก่อนทุกครั้ง และจะมีการสร้างข้อความทั้งหมด 10 ครั้ง เทรดที่ทำหน้าที่เป็น Producer จะต้องตรวจสอบก่อนว่า ตัวแปร flag เป็น false หรือไม่ จึงจะเขียนข้อความใหม่ลงไป และเทรดที่เป็น Consumer จะต้องตรวจสอบค่าของตัวแปรนี้เช่นกัน แต่เมื่อนำข้อความไปใช้แสดงผลแล้วเปลี่ยนค่า ของตัวแปร flag เป็น true #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <stdbool.h> 5 #include <pthread.h> 6 7 // global variables 8 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 9 static char message[32]; // shared variable 10 static bool running = true; 11 static bool flag = false; 12 13 void *producer_func( void *arg ) { 14 int cnt = 1; 15 while (cnt <= 10) { 16 // blocking call 17 pthread_mutex_lock( &lock ); 18 if (!flag) { 19 snprintf( message, sizeof(message), 20 "hello #%d", cnt++ ); 21 printf( "Producer> %sn", message ); 22 flag = true; 23
  • 8. } 24 pthread_mutex_unlock( &lock ); 25 usleep( 1000 + (rand() % 1000) ); 26 } 27 return NULL; 28 } 29 30 void *consumer_func( void *arg ) { 31 while (running) { 32 // blocking call 33 pthread_mutex_lock( &lock ); 34 if ( flag ) { 35 printf( "Consumer> %snn", message ); 36 message[0] = '0'; // clear message string 37 flag = false; 38 usleep( (rand() % 1000)*1000 ); 39 } 40 pthread_mutex_unlock( &lock ); 41 usleep(1); 42 } 43 return NULL; 44 } 45 46 int main( int argc, char *argv[] ) { 47 int retval; 48 pthread_t producer_thread, consumer_thread; 49 50 // initialize the pseudorandom generator with a seed 51 srand( time(NULL) ); 52 53 // clear message buffer 54 memset( message, 0, sizeof(message) ); 55 56 // create a mutex lock 57 if ( pthread_mutex_init( &lock, NULL ) ) { 58 printf( "Mutex init failed!n" ); 59 exit(1); 60 } 61 62 // create producer and consumer threads 63 retval = pthread_create( 64 &producer_thread, NULL, 65 producer_func, (void*) NULL ); 66 if (retval) { 67 printf( "Create producer thread failed!n" ); 68 exit(1); 69 } 70 retval = pthread_create( 71 &consumer_thread, NULL, 72 consumer_func, (void*) NULL ); 73 if (retval) { 74 printf( "Create consumer thread failed!n" ); 75 exit(1); 76 } 77 78 usleep( 10000 /*usec*/ ); // sleep for 10msec before proceeding 79 80 // wait for producer thread 81 pthread_join( producer_thread, NULL ); 82 running = false; // change running flag to false 83 pthread_cancel( consumer_thread ); // cancel consumer thread 84 pthread_join( consumer_thread, NULL ); // wait for consumer thread 85 pthread_mutex_destroy( &lock ); // destroy mutex lock 86 printf( "Done...nn" ); 87 return 0; 88 } 89 ในตัวอย่างนี้ เทรด Producer จะจบการทำงานเอง เมื่อได้สร้างข้อความตามจำนวนที่กำหนดไว้ครบแล้ว แต่สำหรับการ ทำงานของเทรด Consumer จะมีการใช้คำสั่ง pthread_cancel(...) เพื่อขอให้จบการทำงานหลังจากนั้น
  • 9. ถ้าจะลองเปลี่ยนมาใช้คำสั่ง pthread_mutex_trylock(...) สำหรับฟังก์ชันการทำงานของเทรด ก็มีตัวอย่างโค้ดดังนี้ void *producer_func( void *arg ) { 1 int cnt = 1; 2 while (cnt <= 10) { 3 // blocking call 4 while( pthread_mutex_trylock( &lock ) ) { 5 usleep( 1000 ); 6 } 7 if (!flag) { 8 snprintf( message, sizeof(message), 9 "hello #%d", cnt++ ); 10 printf( "Producer> %sn", message ); 11 flag = true; 12 } 13 pthread_mutex_unlock( &lock ); 14 usleep( 1000 + (rand() % 1000) ); 15 } 16 return NULL; 17 } 18 19 void *consumer_func( void *arg ) { 20 while (running) { 21 // blocking call 22 while( pthread_mutex_trylock( &lock ) ) { 23 usleep( 1000 ); 24 } 25 if ( flag ) { 26 printf( "Consumer> %snn", message ); 27 message[0] = '0'; // clear message string 28 flag = false; 29 usleep( (rand() % 1000)*1000 ); 30 } 31 pthread_mutex_unlock( &lock ); 32 usleep(1); 33 } 34 return NULL; 35 } 36 ตัวอย่างที่ 4: การใช้ Condition Variable ถ้าต้องการให้เทรดหยุดรอ (Waiting / Blocked) เหตุการณ์ตามเงื่อนไขที่กำหนดไว้จนกว่าเงื่อนไขจะเป็นจริง แล้วจึง ให้ทำงานต่อไป ในสถานการณ์เช่นนี้ เราสามารถใช้ตัวแปรประเภทหนึ่งที่เรียกว่า "ตัวแปรเงื่อนไข" (Condition Variable) และการทำให้เงื่อนไขเป็นจริงได้นั้น จะต้องมีการส่งสัญญาณ (Signaling) จากเทรดอื่นมา ตัวแปรเงื่อนไขจึง เป็นอีกวิธีการหนึ่งในการสื่อสารกันระหว่างเทรด (Inter-Thread Communication) ในไลบรารีของ POSIX Pthread ก็มีคำสั่งสำหรับการใช้งาน Condition Variable (ชนิดข้อมูล pthread_cond_t) โดย จะต้องใช้ร่วมกับ Mutex (ชนิดข้อมูล pthread_mutex_t) และคำสั่งที่เกี่ยวข้องได้แก่ pthread_cond_init(...) สร้างและเริ่มต้นใช้งานตัวแปรเงื่อนไข pthread_cond_wait(...) รอจนกว่าตัวแปรเงื่อนไขจะเป็นจริง pthread_cond_timedwait(...) รอจนกว่าตัวแปรเงื่อนไขจะเป็นจริง แต่มีระยะเวลาจำกัดในการรอ pthread_cond_signal(...) ส่งสัญญาณไปยังเทรดที่รออยู่ เพื่อระบุว่าตัวแปรเงื่อนไขเป็นจริง และเทรดที่รอ เงื่อนไขอยู่จะทำงานต่อได้ pthread_cond_broadcast(...) ส่งสัญญาณไปยังทุกเทรดที่รออยู่ เพื่อระบุว่าตัวแปรเงื่อนไขเป็นจริง pthread_cond_destroy(...) เลิกใช้ตัวแปรแบบเงื่อนไข ตัวอย่างนี้สาธิตการสร้างเทรดขึ้นมาใหม่ และให้เทรดดังกล่าวรอให้มีการส่งสัญญาณมาจากเทรดหลักในฟังก์ชัน main() และจะเพิ่มค่าตัวนับของตัวแปรภายในและแสดงข้อความตัวเลขดังกล่าว การส่งสัญญาณสำหรับตัวแปรเงื่อนไข ในตัวอย่างจะเกิดขึ้นทั้งหมด 10 ครั้ง
  • 10. #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 5 // global variables 6 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 7 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 8 9 void *thread_func( void *arg ) { 10 int ticks = 0; 11 while (1) { 12 pthread_mutex_lock( &mutex ); 13 // wait for signal 14 if ( !pthread_cond_wait( &cond, &mutex ) ) { 15 printf( "ticks: %dn", ++ticks ); 16 } 17 pthread_mutex_unlock( &mutex ); 18 } 19 printf( "Thread exited..n" ); 20 return NULL; 21 } 22 23 int main( int argc, char *argv[] ) { 24 int retval; 25 pthread_t thread; 26 27 // create a mutex lock 28 if ( pthread_mutex_init( &mutex, NULL ) ) { 29 printf( "Mutex init failed!n" ); 30 exit(1); 31 } 32 // create a condition variable 33 if ( pthread_cond_init( &cond, NULL ) ) { 34 printf( "Cond init failed!n" ); 35 exit(1); 36 } 37 // create a thread 38 retval = pthread_create( 39 &thread, NULL, thread_func, (void*) NULL ); 40 if (retval) { 41 printf( "Create a new thread failed!n" ); 42 exit(1); 43 } 44 45 usleep(10); 46 for ( int i=0; i < 10; i++ ) { 47 pthread_mutex_lock( &mutex ); 48 pthread_cond_signal( &cond ); 49 pthread_mutex_unlock( &mutex ); 50 sleep(1); 51 } 52 pthread_cancel( thread ); 53 pthread_join( thread, NULL ); 54 pthread_cond_destroy( &cond ); 55 pthread_mutex_destroy( &mutex ); // destroy mutex lock 56 printf( "Done...nn" ); 57 return 0; 58 } 59 แต่ถ้าจะลองเปลี่ยนมาใช้คำสั่ง pthread_cond_timewait(...) เพื่อให้เทรดสามารถรอเงื่อนไข แต่มีระยะเวลาจำกัด (Timeout) เช่น 1 วินาที แล้วจึงตรวจสอบซ้ำ ก็มีตัวอย่างโค้ดดังนี้ #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3
  • 11. #include <time.h> 4 #include <pthread.h> 5 6 // global variables 7 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 8 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 9 static struct timespec ts; 10 11 void *thread_func( void *arg ) { 12 int ticks = 0; 13 int retval; 14 time_t now; 15 while (1) { 16 pthread_mutex_lock( &mutex ); 17 // wait for signal with timeout (1 second) 18 time( &now ); // get current timestamp 19 ts.tv_sec = now + 1; // plus 1 second 20 ts.tv_nsec = 0; 21 retval = pthread_cond_timedwait( &cond, &mutex, &ts ); 22 if ( retval == 0 ) { 23 printf( "ticks: %dn", ++ticks ); 24 } else { // timeout 25 time( &now ); 26 printf( "timeout at %sn", ctime(&now) ); 27 } 28 pthread_mutex_unlock( &mutex ); 29 } 30 printf( "Thread exited..n" ); 31 return NULL; 32 } 33 34 int main( int argc, char *argv[] ) { 35 int retval; 36 pthread_t thread; 37 38 srand( time(NULL) ); 39 40 // create a mutex lock 41 if ( pthread_mutex_init( &mutex, NULL ) ) { 42 printf( "Mutex init failed!n" ); 43 exit(1); 44 } 45 // create a condition variable 46 if ( pthread_cond_init( &cond, NULL ) ) { 47 printf( "Cond init failed!n" ); 48 exit(1); 49 } 50 // create a thread 51 retval = pthread_create( 52 &thread, NULL, thread_func, (void*) NULL ); 53 if (retval) { 54 printf( "Create a new thread failed!n" ); 55 exit(1); 56 } 57 58 usleep(10); 59 for ( int i=0; i < 10; i++ ) { 60 pthread_mutex_lock( &mutex ); 61 pthread_cond_signal( &cond ); 62 pthread_mutex_unlock( &mutex ); 63 usleep( (rand()%2000)*1000 ); 64 } 65 pthread_cancel( thread ); 66 pthread_join( thread, NULL ); 67 pthread_cond_destroy( &cond ); // destroy condition variable 68 pthread_mutex_destroy( &mutex ); // destroy mutex lock 69 printf( "Done...nn" ); 70 return 0; 71 } 72
  • 12. ถ้าจะลองใช้ตัวแปรเงื่อนไขกับตัวอย่างการสื่อสารกันระหว่างเทรดแบบ Producer-Consumer ก็มีตัวอย่างโค้ดดังนี้ การใช้ตัวแปรเงื่อนไขโดยแยกสำหรับเทรด Producer และเทรด Consumer แต่มีการใช้Mutex ร่วมกัน เทรด Producer จะต้องรอตัวแปรเงื่อนไข cond_p และมีการส่งสัญญาณจากเทรด Consumer เทรด Consumer จะต้องรอตัวแปรเงื่อนไข cond_c และมีการส่งสัญญาณจากเทรด Producer #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3 #include <string.h> 4 #include <stdbool.h> 5 #include <time.h> 6 #include <pthread.h> 7 8 // global variables 9 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 10 static pthread_cond_t cond_c = PTHREAD_COND_INITIALIZER; 11 static pthread_cond_t cond_p = PTHREAD_COND_INITIALIZER; 12 static char message[32] = {'0'}; 13 static bool flag = false; 14 15 void *producer_thread_func( void *arg ) { 16 int ticks = 0; 17 while (1) { 18 pthread_mutex_lock( &mutex ); 19 while ( flag ) { // wait for signal from consumer thread 20 pthread_cond_wait( &cond_p, &mutex ); 21 } 22 flag = true; 23 snprintf( message, sizeof(message), "test %d", ++ticks ); 24 printf( "Producer> %sn", message ); 25 // send signal to consumer thread 26 pthread_cond_signal( &cond_c ); 27 pthread_mutex_unlock( &mutex ); 28 usleep( (1+rand()%1000)*1000 ); 29 if (ticks >= 10) break; 30 } 31 printf( "Producer thread exited..n" ); 32 return NULL; 33 } 34 35 void *consumer_thread_func( void *arg ) { 36 while (1) { 37 pthread_mutex_lock( &mutex ); 38 while ( !flag ) { // wait for signal from consumer thread 39 pthread_cond_wait( &cond_c, &mutex ); 40 } 41 flag = false; 42 printf( "Consumer> %snn", message ); 43 message[0] = '0'; 44 // send signal to producer thread 45 pthread_cond_signal( &cond_p ); 46 pthread_mutex_unlock( &mutex ); 47 usleep( (1+rand()%1000)*1000 ); 48 } 49 printf( "Consumer thread exited..n" ); 50 return NULL; 51 } 52 53 int main( int argc, char *argv[] ) { 54 int retval; 55 pthread_t thread_p, thread_c; 56 57 srand( time(NULL) ); 58 59 // create a mutex lock 60 if ( pthread_mutex_init( &mutex, NULL ) ) { 61
  • 13. printf( "Mutex init failed!n" ); 62 exit(1); 63 } 64 // create condition variables 65 if ( pthread_cond_init( &cond_p, NULL ) ) { 66 printf( "Cond init failed!n" ); 67 exit(1); 68 } 69 if ( pthread_cond_init( &cond_c, NULL ) ) { 70 printf( "Cond init failed!n" ); 71 exit(1); 72 } 73 // create Producer thread 74 retval = pthread_create( 75 &thread_p, NULL, 76 producer_thread_func, 77 (void*) NULL ); 78 if (retval) { 79 printf( "Create producer thread failed!n" ); 80 exit(1); 81 } 82 // create Consumer thread 83 retval = pthread_create( 84 &thread_c, NULL, 85 consumer_thread_func, 86 (void*) NULL ); 87 if (retval) { 88 printf( "Create consumer thread failed!n" ); 89 exit(1); 90 } 91 92 pthread_join( thread_p, NULL ); 93 pthread_cancel( thread_c ); 94 pthread_join( thread_c, NULL ); 95 pthread_cond_destroy( &cond_p ); 96 pthread_cond_destroy( &cond_c ); 97 pthread_mutex_destroy( &mutex ); 98 printf( "Done...nn" ); 99 return 0; 100 } 101 ถ้าจะลองใช้คำสั่ง pthread_cond_broadcast() เพื่อส่งสัญญาณไปยังทุกเทรดที่มีหลายเทรด (ตามจำนวนที่กำหนด โดย NUM_THREADS) และรอตัวแปรเงื่อนไขเดียวกัน ก็มีตัวอย่างดังนี้ #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3 #include <stdbool.h> 4 #include <time.h> 5 #include <pthread.h> 6 7 #define NUM_THREADS (4) 8 9 // global variables 10 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 11 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 12 static bool is_running = true; 13 14 void *thread_func( void *arg ) { 15 long id = (long)arg; 16 int ticks = 0; 17 while (is_running) { 18 pthread_mutex_lock( &mutex ); 19 pthread_cond_wait( &cond, &mutex ); 20 printf( "Thread: #%ld, ticks=%dn", id, ++ticks ); 21 pthread_mutex_unlock( &mutex ); 22 usleep(10); 23 } 24
  • 14. sleep(1); 25 printf( "nThread #%ld exited..n", id ); 26 return NULL; 27 } 28 29 int main( int argc, char *argv[] ) { 30 int retval; 31 pthread_t threads[ NUM_THREADS ]; 32 33 srand( time(NULL) ); 34 35 // create a mutex lock 36 if ( pthread_mutex_init( &mutex, NULL ) ) { 37 printf( "Mutex init failed!n" ); 38 exit(1); 39 } 40 // create a condition variable 41 if ( pthread_cond_init( &cond, NULL ) ) { 42 printf( "Cond init failed!n" ); 43 exit(1); 44 } 45 // create threads 46 for ( int i=0; i < NUM_THREADS; i++ ) { 47 retval = pthread_create( 48 &threads[i], NULL, thread_func, 49 (void*) (long)(i+1) ); 50 if (retval) { 51 printf( "Create a new thread failed!.n" ); 52 exit(1); 53 } 54 } 55 56 usleep( 1000 ); 57 int N = 10; 58 for ( int i=0; i < N; i++ ) { 59 pthread_mutex_lock( &mutex ); 60 is_running = (i==N-1) ? false : true; 61 pthread_cond_broadcast( &cond ); 62 pthread_mutex_unlock( &mutex ); 63 printf("-------------------------n"); 64 usleep( (rand()%1000)*1000 ); 65 } 66 67 sleep(1); 68 for (int i=0; i < NUM_THREADS; i++) { 69 pthread_join( threads[i], NULL ); 70 } 71 pthread_cond_destroy( &cond ); // destroy condition variable 72 pthread_mutex_destroy( &mutex ); // destroy mutex lock 73 printf( "Done...nn" ); 74 return 0; 75 } 76 ตัวอย่างที่ 5: การใช้ Barrier ในกรณีที่มีการสร้างเทรดขึ้นมาทำงานไปพร้อม ๆ กัน แต่จะต้องมีการรอจังหวะให้ทุกเทรดทำงานในแต่ละขั้นตอนย่อย เสร็จครบทุกเทรด แล้วจึงอนุญาตให้ทุกเทรดสามาารถทำงานในขั้นตอนต่อไปได้เราจะใช้สิ่งที่เรียกว่า Barrier เปรียบ เสมือนแผงกั้นให้ทุกเทรดมาถึงจนครบตามจำนวนที่กำหนดไว้จึงเปิดขึ้นให้ทำงานต่อไปได้ การใช้POSIX Pthreads ก็มีชนิดของข้อมูล แต่คำสั่งที่เกี่ยวข้องดังนี้ pthread_barrier_init() สร้าง Barrier สำหรับใช้งาน pthread_barrier_wait() รอจนกว่าจะได้รับการแจ้งให้ผ่านไปได้ pthread_barrier_destroy() เลิกใช้Barrier
  • 15. ตัวอย่างโค้ดสาธิตการใช้Barrier มีดังนี้ โดยมีการสร้างเทรดขึ้นมาตามจำนวนที่กำหนด (NUM_THREADS) และทุก เทรดที่สร้างขึ้นมานั้นรวมถึงเทรดหลักจะต้องรอจังหวะกันโดยใช้คำสั่ง pthread_barrier_wait() #include <stdio.h> 1 #include <stdlib.h> 2 #include <unistd.h> 3 #include <stdbool.h> 4 #include <time.h> 5 #include <pthread.h> 6 7 #define NUM_THREADS (4) 8 9 // global variables 10 static pthread_barrier_t barrier; 11 static bool is_running = true; 12 13 void *thread_func( void *arg ) { 14 long id = (long)arg; 15 int ticks = 0; 16 while (is_running) { 17 pthread_barrier_wait( &barrier ); 18 printf( "Thread: #%ld, ticks=%dn", id, ++ticks ); 19 usleep( (1+rand()%2000)*1000 ); 20 } 21 sleep(1); 22 printf( "nThread #%ld exited..n", id ); 23 return NULL; 24 } 25 26 int main( int argc, char *argv[] ) { 27 int retval; 28 pthread_t threads[ NUM_THREADS ]; 29 30 srand( time(NULL) ); 31 32 // create a barrier 33 if (pthread_barrier_init( &barrier, NULL, NUM_THREADS+1) ) { 34 printf( "Barrier init failed!n" ); 35 exit(1); 36 } 37 // create threads 38 for (int i=0; i < NUM_THREADS; i++ ) { 39 retval = pthread_create( 40 &threads[i], NULL, thread_func, 41 (void*) (long)(i+1) ); 42 if (retval) { 43 printf( "Create a new thread failed!n" ); 44 exit(1); 45 } 46 } 47 48 for ( int i=0; i < 10; i++ ) { 49 pthread_barrier_wait( &barrier ); 50 } 51 52 is_running = false; 53 for (int i=0; i < NUM_THREADS; i++) { 54 pthread_join( threads[i], NULL ); 55 } 56 pthread_barrier_destroy( &barrier ); // destroy barrier 57 printf( "Done...nn" ); 58 return 0; 59 } 60
  • 16. ตัวอย่างโค้ด C++ สาธิตการใช้งาน std::thread ตัวอย่างที่ 1: Thread Creation โค้ดในตัวอย่างนี้สาธิตการสร้างเทรด (std::thread) จำนวน 2 เทรด (thread1 และ thread2) และเมื่อเทรดทำงาน จะ ต้องมีการล็อคการใช้งานของ Mutex (std::mutex) ให้ได้ก่อนแล้วจึงจะแสดงหมายเลขของเทรดเป็นข้อความ จากนั้น จะมีการรอเวลาซึ่งมีระยะเวลาตามตัวเลขสุ่มในช่วง 1000 ถึง 2000 (มิลลิวินาที) แล้วจึงปลดล็อค Mutex // g++ -std=c++14 main.cc -o main -lpthread 1 2 #include <iostream> // for std::cout 3 #include <thread> // for std:thread 4 #include <mutex> // for std::mutex 5 #include <cstdlib> // for std::srand(), std::rand() 6 #include <vector> // for std::vector() 7 8 using namespace std::chrono; 9 using namespace std::literals::chrono_literals; 10 11 // global variable 12 std::mutex output_lock; 13 14 static void thread_func( long arg ) { 15 output_lock.lock(); 16 std::cout << "Thread #" << arg << "n"; 17 // generate a random integer value between 1000..2000 18 long msec = 1000 + (std::rand() % 1000); 19 std::this_thread::sleep_for( 20 std::chrono::milliseconds(msec) ); 21 output_lock.unlock(); 22 } 23 24 int main() { 25 std::srand( std::time(nullptr) ); // set seed value 26 27 // create two threads 28 std::thread thread1( thread_func, 1 ); 29 std::thread thread2( thread_func, 2 ); 30 // sleep for 1 second 31 std::this_thread::sleep_for( 1s ); 32 // wait for threads 1 and 2 33 if( thread1.joinable() ) { 34 thread1.join(); 35 } 36 if( thread2.joinable() ) { 37 thread2.join(); 38 } 39 return 0; 40 } 41 ตัวอย่างที่ 2: Mutex การเขียนโค้ดภาษา C++ นั้น มีความหลากหลายมากกว่าภาษา C เนื่องจากรองรับการเขียนโปรแกรมแบบเชิงวัตถุ และมี การสร้างคลาส C++ ไว้สำหรับใช้งานได้เป็นมาตรฐาน ตัวอย่างโค้ดต่อไปนี้ ให้ผลการทำงานเหมือนโค้ดตัวอย่างแรก แต่มีการเพิ่มจำนวนเทรดให้มีมากกว่า 2 ได้และมีรูปแบบการเขียนที่แตกต่างกันออกไป เช่น มีการใช้เวกเตอร์ (std::vector) สำหรับอ้างอิงออปเจกต์ของเทรด (std::thread) ที่ได้มีการสร้างขึ้นมา และมีการสร้างฟังก์ชันสำหรับ เทรดแต่ละเทรด ให้อยู่ในรูปแบบที่เรียกว่า Anonymous Function // g++ -std=c++14 main.cc -o main -lpthread 1 2
  • 17. #include <iostream> // for std::cout 3 #include <thread> // for std:thread 4 #include <mutex> // for std::mutex 5 #include <cstdlib> // for std::srand(), std::rand() 6 #include <vector> // for std::vector 7 #include <algorithm> // for std::for_each() 8 9 using namespace std::chrono; 10 using namespace std::literals::chrono_literals; 11 12 // global variable 13 std::mutex output_lock; 14 15 int main() { 16 std::srand( std::time(nullptr) ); 17 18 const int NUM_THREADS = 10; 19 // use a vector container stores threads 20 std::vector<std::thread> workers; // worker threads 21 for ( int i=0; i < NUM_THREADS; i++ ) { 22 // create a thread with an anonymous function 23 // and add it to the vector container 24 workers.push_back( std::thread ( [](long arg){ 25 output_lock.lock(); 26 std::cout << "Thread #" << arg << "n"; 27 // generate a random integer value between 1000..2000 28 long msec = 1000 + (std::rand() % 1000); 29 std::this_thread::sleep_for( 30 std::chrono::milliseconds(msec) ); 31 output_lock.unlock(); 32 }, i+1) ); 33 } 34 35 // sleep for 1 second 36 std::this_thread::sleep_for(1s); 37 38 // wait for worker threads 39 std::for_each(workers.begin(),workers.end(),[](std::thread& t) { 40 if (t.joinable()) { 41 t.join(); 42 } 43 }); 44 return 0; 45 } 46 47 ตัวอย่างที่ 3: Condition Variable โค้ดในตัวอย่างนี้สาธิตการใช้งาน Mutex (std::mutex) ร่วมกับตัวแปรเงื่อนไข (std::condition_variable) โดยให้ เทรดหลักของฟังก์ชัน main() คอยส่งสัญญาณโดยใช้คำสั่ง notify_onice(...) ไปยังอีกเทรดหนึ่งที่ถูกสร้างขึ้นใหม่ (เป็นการส่งสัญญาณรอจังหวะในทิศทางเดียว) และให้เทรดนั้นรอสัญญาณแบบไม่จำกัดเวลาโดยใช้คำสั่ง wait(...) สำหรับตัวแปรเงื่อนไข ในการทำงานแต่ละรอบของลูป while // g++ -std=c++14 main.cc -o main -lpthread 1 2 #include <iostream> // for std::cout 3 #include <thread> // for std:thread 4 #include <mutex> // for std::mutex 5 #include <condition_variable> // for std::condition_variable 6 7 using namespace std::chrono; 8 using namespace std::literals::chrono_literals; 9 10 // global variable 11 std::mutex mtx; 12 std::condition_variable cv; 13
  • 18. bool running = true; 14 bool ready = false; 15 16 void thread_func() { 17 int ticks = 0; 18 while (running) { 19 std::unique_lock<std::mutex> lock(mtx); 20 while (!ready) { 21 cv.wait( lock ); 22 } 23 ready = false; 24 if (running) { 25 std::cout << "Thread: ticks=" << (++ticks) << "n"; 26 } 27 // The mutex lock is automatically released after each while loop. 28 } 29 } 30 31 int main() { 32 // create a thread 33 std::thread t( thread_func ); 34 35 for ( int i=0; i < 10; i++ ) { 36 mtx.lock(); 37 ready = true; 38 cv.notify_one(); 39 mtx.unlock(); 40 std::this_thread::sleep_for(500ms); 41 } 42 43 mtx.lock(); 44 ready = true; 45 running = false; 46 cv.notify_one(); 47 mtx.unlock(); 48 if ( t.joinable() ) { 49 t.join(); 50 } 51 return 0; 52 } 53 ถ้าจะเปลี่ยนมาใช้คำสั่ง wait_for(...) ซึ่งเป็นการรอที่มีระยะเวลาจำกัด (Timeout) สำหรับตัวแปรเงื่อนไข ก็มีตัวอย่าง โค้ดดังนี้ // g++ -std=c++14 main.cc -o main -lpthread 1 2 #include <iostream> // for std::cout 3 #include <thread> // for std:thread 4 #include <mutex> // for std::mutex 5 #include <condition_variable> // for std::condition_variable 6 7 using namespace std::chrono; 8 using namespace std::literals::chrono_literals; 9 10 // global variable 11 std::mutex mtx; 12 std::condition_variable cv; 13 bool running = true; 14 bool ready = false; 15 16 void thread_func() { 17 int ticks = 0; 18 while (running) { 19 std::unique_lock<std::mutex> lock(mtx); 20 if ( cv.wait_for(lock,milliseconds(10))!=std::cv_status::timeout ){ 21 if (ready) { 22 std::cout << "Thread: ticks=" << (++ticks) << "n"; 23 ready = false; 24
  • 19. } 25 } 26 // The mutex lock is automatically released after each while loop. 27 } 28 } 29 30 int main() { 31 // create a thread 32 std::thread t( thread_func ); 33 34 for ( int i=0; i < 10; i++ ) { 35 mtx.lock(); 36 ready = true; 37 cv.notify_one(); 38 mtx.unlock(); 39 std::this_thread::sleep_for(500ms); 40 } 41 42 mtx.lock(); 43 ready = true; 44 running = false; 45 cv.notify_one(); 46 mtx.unlock(); 47 if ( t.joinable() ) { 48 t.join(); 49 } 50 return 0; 51 } 52 ปรับปรุงแก้ไขล่าสุด: 2021-08-29