Threading Programming

ตัวอย่างที่ 1

แสดงตัวอย่างการสร้าง thread อย่างง่ายโดยการเรียกใช้ฟังก์ชัน pthread_create()

// thread1.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* print_message_function(void *ptr) {
	char *message;
	message = (char*) ptr;
	printf("[Thread started] %s \n", message);
}

main() {
	pthread_t thread;
	const char *message = "Hello World...";
	int iret;
	iret = pthread_create(&thread, NULL, print_message_function,
			(void*) message);
	/* Wait till thread is complete before main continues. Unless we  */
	/* wait we run the risk of executing an exit which will terminate   */
	/* the process and thread before the thread has completed.   */
	pthread_join(thread, NULL);
	printf("Thread returns: %d\n", iret);
	exit(0);
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread1 thread1.c -lpthread
$ ./thread1 
[Thread started] Hello world...
Thread returns: 0

ตัวอย่างที่ 2

แสดงตัวอย่างการสร้าง thread ขึ้นมา 4 ตัวจากฟังก์ชัน *printme(void *ip) เดียวกัน โดยแต่ละ thread ก็จะแสดงข้อความเพียงแจ้งว่าตัวเองเป็น thread ตัวที่เท่าไหร่ (0-3) ซึ่งถ้าโปรแกรมทำงานอยู่บนเครื่องคอมพิวเตอร์ที่มีหน่วยประมวลผลเป็นแบบหลายตัว (multiprocessors) ก็จะทำให้ประสิทธิภาพการทำงานในการประมวลผลรวดเร็วยิ่งขึ้น เพราะแต่ละตัวจะแยกใช้งานไปแต่ละตัวหน่วยประมวลผล

// thread2.c
// forks off four threads that print their ids
#include <pthread.h>
#include <stdio.h>

void* printme(void *ip) {
	int *i;

	i = (int*) ip;
	printf("Hi.  I'm thread %d\n", *i);
	return NULL;
}

main() {
	int i, vals[4];
	pthread_t tids[4];
	void *retval;

	for (i = 0; i < 4; i++) {
		vals[i] = i;
		pthread_create(tids + i, NULL, printme, vals + i);
	}

	for (i = 0; i < 4; i++) {
		printf("Trying to join with tid %d\n", i);
		pthread_join(tids[i], &retval);
		printf("Joined with tid %d\n", i);
	}
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread2 thread2.c -lpthread
$ ./thread2 
Trying to join with tid 0
Hi.  I'm thread 1
Hi.  I'm thread 2
Hi.  I'm thread 3
Hi.  I'm thread 0
Joined with tid 0
Trying to join with tid 1
Joined with tid 1
Trying to join with tid 2
Joined with tid 2
Trying to join with tid 3
Joined with tid 3

จากผลลัพธ์จะสังเกตเห็นว่าเมื่อเริ่มเรียกฟังก์ชัน pthread_join() ดังนั้นตัว thread ตัวที่ 1 (อาจจะไม่ได้เรียงตามลำดับหมายเลขทุกครั้งไป เนื่องจากขึ้นอยู่กับว่าตัว thread ใดเข้าครอบครองหน่วยประมวลผลเป็นคนแรก) ก็จะเริ่มเข้าใช้งานหน่วยประมวลผล แล้วแสดงข้อความว่า “Hi. I'm thread x” เมื่อการทำงานเสร็จสิ้น ก็จะเป็นคิวของตัว thread ถัดๆไปจนครบทั้ง 4 ตัว ก็จะกลับมายังฟังก์ชันหลัก (main) เพื่อสิ้นสุดการทำงานของโปรแกรม

ตัวอย่างที่ 3

แสดงตัวอย่างการส่งผ่านข้อความ (char *) ให้กับเทรด ซึ่งตัวอย่างก่อนหน้านี้เป็นการส่งข้อมูลที่เป็นตัวแปรจำนวนเต็มทั่วไป

// thread3.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* print_message_function(void *ptr);
main() {
	pthread_t thread1, thread2;
	const char *message1 = "Thread 1’s Message";
	const char *message2 = "Thread 2’s Message";
	int iret1, iret2;
	/* Create independent threads each of which will execute function */
	iret1 = pthread_create(&thread1, NULL, print_message_function,
			(void*) message1);
	iret2 = pthread_create(&thread2, NULL, print_message_function,
			(void*) message2);
	/* Wait till threads are complete before main continues. Unless we  */
	/* wait we run the risk of executing an exit which will terminate   */
	/* the process and all threads before the threads have completed.   */
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);
	printf("Thread 1 returns: %d\n", iret1);
	printf("Thread 2 returns: %d\n", iret2);
	exit(0);
}
void* print_message_function(void *ptr) {
	char *message;
	message = (char*) ptr;
	printf("%s \n", message);
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread3 thread3.c –lpthread
$ ./thread3 
Thread 2’s Message 
Thread 1’s Message
Thread 1 returns: 0
Thread 2 returns: 0

ตัวอย่างที่ 4

แสดงตัวอย่างการใช้ pthread_exit() เพื่อทำให้ thread สิ้นสุดการทำงาน แต่จะยังคงรักษาให้งาน (task) ยังทำงานอยู่ ถ้า thread ทั้งหมดสิ้นสุดลงแล้วงาน (task) ก็จะสิ้นสุดลงเช่นกัน

// thread4.c
#include <pthread.h>
#include <stdio.h>

void* printme(void *v) {
	int *i;

	i = (int*) v;
	printf("Hi.  I'm thread %d\n", *i);
	pthread_exit(NULL);
}

main() {
	int i, vals[4];
	pthread_t tids[4];
	void *retval;

	for (i = 0; i < 4; i++) {
		vals[i] = i;
		pthread_create(tids + i, NULL, printme, (void*) (vals + i));
	}

	for (i = 0; i < 4; i++) {
		printf("Trying to join with tid %d\n", i);
		pthread_join(tids[i], &retval);
		printf("Joined with tid %d\n", i);
	}
	pthread_exit(NULL);
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread4 thread4.c -lpthread
$ ./thread4
Trying to join with tid 0
Hi.  I'm thread 3
Hi.  I'm thread 2
Hi.  I'm thread 1
Hi.  I'm thread 0
Joined with tid 0
Trying to join with tid 1
Joined with tid 1
Trying to join with tid 2
Joined with tid 2
Trying to join with tid 3
Joined with tid 3

ผลลัพธ์จากการรันโปรแกรม thread4 จะได้ผลเช่นเดียวกับโปรแกรม thread2 จากตัวอย่างที่ 2

ตัวอย่างที่ 5

แสดงตัวอย่างการเรียก exit() ในฟังก์ชันของ thread แทนที่จะเรียก pthread_exit()

// thread5.c
#include <pthread.h>
#include <stdio.h>

void* printme(i)
	int *i; {
	printf("Hi.  I'm thread %d\n", *i);
	exit(NULL);
}

main() {
	int i, vals[4];
	pthread_t tids[4];
	void *retval;

	for (i = 0; i < 4; i++) {
		vals[i] = i;
		pthread_create(tids + i, NULL, printme, vals + i);
	}

	for (i = 0; i < 4; i++) {
		printf("Trying to join with tid %d\n", i);
		pthread_join(tids[i], &retval);
		printf("Joined with tid %d\n", i);
	}
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread5 thread5.c -lpthread
$ ./thread5 
Trying to join with tid 0
Hi.  I'm thread 3

ผลลัพธ์จากการรันโปรแกรม thread5 จะสังเกตเห็นว่ามีการเรียกฟังก์ชัน exit() ภายในฟังก์ชัน thread ทำให้เมื่อมี thread ตัวใดตัวหนึ่งเริ่มเข้าครอบครองหน่วยประมวลผลกลาง เมื่อทำงานเสร็จ (แสดงข้อความ) ก็จะเรียก exit() เป็นผลให้สิ้นสุดการทำงานของโปรแกรมหลัก รวมทั้ง thread ลูกต่างๆทันที

ตัวอย่างที่ 6

แสดงตัวอย่างการทำงานระหว่าง 2 เทรด ที่พยายามแย่งกันแสดงผลข้อความออกทางหน้าจอ

// thread6.c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

void* thread1() {
	while (1) {
		printf("Hello!!\n");
	}
}

void* thread2() {
	while (1) {
		printf("How are you?\n");
	}
}

int main() {
	int status;
	pthread_t tid1, tid2;

	pthread_create(&tid1, NULL, thread1, NULL);
	pthread_create(&tid2, NULL, thread2, NULL);
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	return 0;
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread6 thread6.c -lpthread 
$ ./thread6 
How are you?
How are you?
How are you?
How are you?
How are you?
How are you?
Hello!!
Hello!!
Hello!!
Hello!!
Hello!!
How are you?
How are you?
Hello!!
How are you?
...

จากผลลัพธ์คำสั่งข้างต้นจะสังเกตเห็นข้อความทั้งสองที่เกิดจากแต่ละเทรดจะแสดงแทรกกันไปมาไม่มีรูปแบบแน่นอน ขึ้นอยู่กับว่าเทรดตัวใด เข้าครอบครองหน่วยประมวลผลก่อนกัน

ตัวอย่างที่ 7

แสดงตัวอย่างการแก้ปัญหาลำดับการแสดงผล โดยให้เทรดแรกได้เข้าถึงทรัพยากรกลางก่อน แล้วให้อีกเทรดถึงไปอ่านค่าในทรัพยากรกลางเป็นลำดับถัดไป ดังตัวอย่างข้างล่างนี้

// thread7.c
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h> 

char msg[1024];
sem_t len;

void* thread_read() {
	while (1) {
		printf("Enter a string: ");
		scanf("%s", msg);
		sem_post(&len);
	}
}

void* thread_write() {
	while (1) {
		sem_wait(&len);
		printf("The string entered is : ");
		printf("==== %s\n", msg);
	}

}

int main() {
	int status;
	pthread_t tr, tw;

	pthread_create(&tr, NULL, thread_read, NULL);
	pthread_create(&tw, NULL, thread_write, NULL);

	pthread_join(tr, NULL);
	pthread_join(tw, NULL);
	return 0;
}

ทำการคอมไพล์ และทดสอบรันโปรแกรม

$ gcc -o thread7 thread7.c -lpthread 
$ ./thread7 
Enter a string: Hello
Enter a string: The string entered is : ==== Hello
Fine?
Enter a string: The string entered is : ==== Fine?
Yes
Enter a string: The string entered is : ==== Yes

จากผลลัพธ์คำสั่งข้างต้น เมื่อมีการใช้การจัดการจังหวะการทำงานเพื่อควบคุมให้แต่เทรดทำงานเป็นลำดับ ก็จะทำให้ผลการทำงานออกถูกต้อง กล่าวคือเมื่อเทรด thread_read() ทำการอ่านค่าจากผู้ใช้แล้วนำไปเก็บในตัวแปร msg โดยที่เทรดอีกตัว thread_write() จะต้องรอให้ตัวแรกทำการเก็บค่าให้เสร็จเรียบร้อยเสียก่อน ถึงจะสามารถเข้าไปอ่านค่าในตัวแปร msg เพื่อมาแสดงผลได้

ตัวอย่างที่ 8

แสดงการประยุกต์การสื่อสารผ่านระบบเครือข่ายด้วยวิธีการ socket programming โดยให้เครื่องแม่ข่ายที่เปิดพอร์ตรอ สามารถรองรับการเชื่อมต่อจากตัวลูกข่ายได้หลายเครื่องพร้อมกัน โดยการใช้ pthread ดังตัวอย่างส่วนของเครื่องแม่ข่ายข้างล่าง

/*
 server.c
 C socket server example, handles multiple clients using threads
 */

#include<stdio.h>
#include<string.h>    //strlen
#include<stdlib.h>    //strlen
#include<sys/socket.h>
#include<arpa/inet.h> //inet_addr
#include<unistd.h>    //write
#include<pthread.h> //for threading , link with lpthread
//the thread function
void* connection_handler(void*);

int main(int argc, char *argv[]) {
	int socket_desc, client_sock, c;
	struct sockaddr_in server, client;

//Create socket
	socket_desc = socket(AF_INET, SOCK_STREAM, 0);
	if (socket_desc == -1) {
		printf("Could not create socket");
	}
	puts("Socket created");

//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons(8888);

//Bind
	if (bind(socket_desc, (struct sockaddr*) &server, sizeof(server)) < 0) {
//print the error message
		perror("bind failed. Error");
		return 1;
	}
	puts("bind done");

//Listen
	listen(socket_desc, 3);

//Accept and incoming connection
	puts("Waiting for incoming connections...");
	c = sizeof(struct sockaddr_in);

//Accept and incoming connection
	puts("Waiting for incoming connections...");
	c = sizeof(struct sockaddr_in);
	pthread_t thread_id;

	while ((client_sock = accept(socket_desc, (struct sockaddr*) &client,
			(socklen_t*) &c))) {
		puts("Connection accepted");

		if (pthread_create(&thread_id, NULL, connection_handler,
				(void*) &client_sock) < 0) {
			perror("could not create thread");
			return 1;
		}

//Now join the thread , so that we dont terminate before the thread
//pthread_join( thread_id , NULL);
		puts("Handler assigned");
	}

	if (client_sock < 0) {
		perror("accept failed");
		return 1;
	}

	return 0;
}
/*
 * This will handle connection for each client
 * */
void* connection_handler(void *socket_desc) {
//Get the socket descriptor
	int sock = *(int*) socket_desc;
	int read_size;
	char *message, client_message[2000];

//Send some messages to the client
	message = "Greetings! I am your connection handler\n";
	write(sock, message, strlen(message));

	message = "Now type something and i shall repeat what you type \n";
	write(sock, message, strlen(message));

//Receive a message from client
	while ((read_size = recv(sock, client_message, 2000, 0)) > 0) {
//end of string marker
		client_message[read_size] = '\0';

//Send the message back to client
		write(sock, client_message, strlen(client_message));

//clear the message buffer
		memset(client_message, 0, 2000);
	}

	if (read_size == 0) {
		puts("Client disconnected");
		fflush(stdout);
	} else if (read_size == -1) {
		perror("recv failed");
	}

	return 0;
}

สำหรับโปรแกรมลูกข่ายดังแสดงข้างล่างนี้

/*
 client.c
 Client was fixed port 8888 for connection to server.
 */

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

int print_usage() {

	printf("\n");
	printf("Usage : client HOST_ADDRESS\n");
	printf("Connect to host by using port 8888 for communication\n");
	printf("\n");

	return 0;
}

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

	struct sockaddr_in serv_addr, cli_addr;
	struct hostent *server;
	int serv_fd, cli_fd;

	char msg_buffer[256];

	if (argc != 2) {
		print_usage();

		return -1;
	}

	system("clear");
	printf("********** CLIENT **********\n");

// 1. create socket
	if ((serv_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		printf("Error : creating socket !!!\n");
	} else {
		printf("1. Creating socket -> OK\n");
	}

// 2. connect to host
	server = gethostbyname(argv[1]);

	bzero((char*) &serv_addr, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(8888);
	bcopy((char*) server->h_addr, (char*) &serv_addr.sin_addr.s_addr,
			server->h_length);

	if (connect(serv_fd, (struct sockaddr*) &serv_addr, sizeof(serv_addr))
			< 0) {
		printf("Error : connecting to server !!!\n");

		return -1;
	} else {
		printf("2. Connecting to server -> OK\n");
	}

// 3. communication 
	printf("********* TALKING **********\n");

// 3.1 receive ready message from server
	bzero(msg_buffer, 256);
	recv(serv_fd, msg_buffer, 256, 0);

	printf("[server] : %s\n", msg_buffer);

// 3.2 answer to server
	bzero(msg_buffer, 256);

	printf("[client] : ");
	scanf("%s", msg_buffer);

	send(serv_fd, msg_buffer, strlen(msg_buffer), 0);

// 3.3 ok communication
	bzero(msg_buffer, 256);
	recv(serv_fd, msg_buffer, 256, 0);

	printf("[server] : %s\n", msg_buffer);

	return 0;
}

คอมไพล์และรันโปรแกรมทั้งสองดังนี้

$ gcc -o server server.c -lpthread
$ ./server
Socket created
bind done
Waiting for incoming connections...
Waiting for incoming connections...

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University