POSIX Thread Anatomy

ภายในระบบปฏิบัติการลีนุกซ์นั้นจะรองรับการทำงานแบบหลายงานพร้อมกัน (multitasking support) โดยตัวโปร-เซสเองจะสามารถสร้างงานย่อยๆได้เรียกส่วนนี้ว่า เทรด (thread) หรืออีกชื่อหนึ่งว่า “lightweight processes” ข้อดีของการใช้ thread คือจะสามารถทำให้นักพัฒนาโปรแกรมทำการจัดการกับเหตุการณ์ที่เกิดขึ้นในเวลาที่ไม่แน่นอน (asynchronous events) ได้อย่างทันทีและมีประสิทธิภาพ นอกจากนั้นสามารถใช้ในการประมวลในลักษณะขนาน (parallel computing) บนเครื่องที่มีหน่วยประมวลผลแบบหลายหน่วยประมวลผล (multi-core CPU) ที่ใช้หน่วยความจำร่วมกันได้ดี (shared-memory multiprocessor)

โดยทั่วไปแล้วโปรเซสภายในระบบปฏิบัติการลีนุกซ์จะประกอบด้วยสถานะของหน่วยประมวลผล (เช่นค่าภายในตัวรีจิสเตอร์ AX,BX,DX) รายละเอียดของการจองหน่วยความจำ (สำหรับเก็บ code, globals, heap และ stack) และรายละเอียดที่เกี่ยวข้องกับระบบปฏิบัติการ (เช่น การเปิดไฟล์, หมายเลขโปรเซส) ซึ่งเทรดก็จะคล้ายกัน แต่จะต่างกันตรงที่โปรเซสแต่ละตัวจะแยกการเก็บสถานะกันอย่างชัดเจน ในขณะที่แต่ละเทรดจะมีการใช้ code, globals และ heap ร่วมกัน

ซึ่งเทรดจะสามารถทำงานได้ดีในกรณีที่เครื่องคอมพิวเตอร์นั้นมีมากกว่าหนึ่งหน่วยประมวลผล โดยในรูปข้างล่างแสดงการทำงานของเทรดแต่ละตัว (ได้แก่ main(), function1() และ function2()) ซึ่งถ้าเครื่องมีหน่วยประมวลผลเพียงตัวเดียวการทำงานของโปรแกรมนี้ก็จะทำตามลำดับของคำสั่งแต่ละบรรทัด (program counter) ซึ่งอาจจะใช้เวลาทั้งสิ้น 2 นาที แต่ถ้าเครื่องมีหน่วยประมวลผลจำนวน 3 ตัวทั้งสามส่วนก็จะแยกกันทำงานไปแต่ละหน่วยประมวลผลซึ่งอาจจะใช้เวลาทั้งสิ้นเพียง 50 วินาทีเท่านั้น

ตัวเทรดแต่ละตัวภายในโปรเซสจะใช้ทรัพยาการเดียวกับโปรเซส แต่อย่างไรก็ตามระบบปฏิบัติการก็สามารถจัดตารางให้การทำงานของแต่ thread ได้อย่างอิสระ เนื่องจากพวกมันเพียงแค่คัดลอกเอาทรัพยากรไปเพียงเล็กน้อยเท่านั้นเพื่อแยกไปเป็นโปรแกรมเล็กอีกตัวดังแสดงในรูปข้างบน

  • การสื่อสารกันระหว่างเทรดจะสามารถติดต่อผ่านกันได้ทางหน่วยความจำที่ใช้งานร่วมกันอยู่ได้ทันที

ในขณะที่

  • การสื่อสารระหว่างโปรเซสจะสามารถติดต่อผ่านกันได้ต้องผ่านทางระบบปฏิบัติการเท่านั้น เช่นผ่านไฟล์, ผ่าน pipe หรือผ่าน socket เป็นต้น

POSIX threads

ไลบรารี POSIX thread ถือว่าเป็นตัวมาตราฐานหลักในการเขียนเทรดสำหรับโปรแกรมภาษา C/C++ ซึ่งเป็นไลบรารีที่จัดเตรียมฟังก์ชันต่างๆเกี่ยวกับการจัดการเทรดและจะทำงานได้มีประสิทธิภาพมากสำหรับระบบคอมพิวเตอร์ที่มีหลายหน่วยประมวลผลกลาง (multi-processor หรือ multi-core systems) เนื่องจากมีฟังก์ชันในการจัดตารางการทำงานของโปรเซสบนแต่ละหน่วยประมวลผลกลางที่อยู่ภายในระบบคอมพิวเตอร์ตัวเดียวกันและเทรดทุกตัวที่อยู่ภายในโปรเซสเดียวกันนั้นก็จะใช้พื้นที่ในหน่วยความจำเดียวกัน (address space) ซึ่งแตกต่างจากเทคโนโลยีในการเขียนโปรแกรมแบบขนาน (Parallel programming) เช่น MPI และ PVM ที่ถูกใช้ในสภาพแวดล้อมการคำนวณแบบกระจาย (distributed computing) ไปยังระบบคอมพิวเตอร์เครื่องอื่นๆ ที่อยู่ไกลออกไปหรือต่างสถานที่กัน

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ไลบรารีที่เกี่ยวข้องคือ pthread.h

  • ฟังก์ชัน int pthread_create(pthread_t *new_thread_ID, const pthread_attr_t *attr, void * (*start_func)(void *), void *arg);

    • สำหรับสร้าง thread ตัวใหม่ (new_thread_ID) และกำหนดฟังก์ชัน (*start_func) ที่จะให้ทำงานเมื่อ thread ถูกเรียกขึ้นมา โดยสามารถระบุพารามิเตอร์ที่จะส่งไปยังฟังก์ชันของ thread ได้ (arg)

  • ฟังก์ชัน int pthread_join(pthread_t target_thread, void **status);

    • สำหรับเรียกให้ thread นั้น (target_thread) ทำงานขึ้นมา

  • ฟังก์ชัน void pthread_exit(void *retval);

    • สำหรับสั่งให้ thread สิ้นสุดการทำงาน

ขบวนการทำงานของเทรดนั้นจะประกอบไปด้วย สร้างเทรด (thread creation), สิ้นสุดการทำงาน (termination), ทำงานตามจังหวะ (thread synchronization ด้วยวิธีการแบบ joins, blocking เป็นต้น), การจัดลำดับการทำงาน (scheduling), การจัดการข้อมูล (data management) และ การติดต่อกันระหว่างกัน (process interaction)

พื้นที่ที่ใช้งานร่วมกันของเทรดทั้งหมดภายในโปรเซสประกอบไปด้วย

  • ชุดคำสั่งโปรเซส (Process instructions)

  • ค่า files descriptors ที่มีการเปิดไว้

  • สัญญาณ (signals) และตัวดำเนินการ (signal handlers)

  • ไดเรดทอรี่ปัจจุบัน (current working directory)

  • หมายเลข User และ Group

โดยแต่ละเทรดจะมี:

  • หมายเลขเทรด (Thread ID)

  • กลุ่มตัวแปรรีจิสเตอร์ (set of registers) และ stack pointer

  • สแต็คสำหรับเก็บค่าตัวแปร (local variables)

  • signal mask

  • ค่า priority

  • ค่าสถานะที่ส่งกลับ: errno

และเมื่อต้องการคอมไพล์โปรแกรมจะต้องมีการอ้างอิงไลบรารี Posix threads ด้วย -lpthread ดังตัวอย่าง

$ gcc -o main main.c –lpthread

เปรียบเทียบประสิทธิภาพระหว่าง fork() และ pthread_create()

เมื่อเปรียบเทียบการสร้างและจัดการโปรเซสแล้ว การใช้ thread จะมีการใช้ทรัพยากรของระบบน้อยกว่ามาก และเกิด overhead กับระบบปฏิบัติการที่น้อยกว่าอย่างเห็นได้ชัด จากตัวอย่างโปรแกรมข้างล่างจะแสดงระยะเวลาของการสร้างโปรเซสและ thread ด้วยจำนวน 50,000 ตัว ภายใต้สภาพแวดล้อมเดียวกัน โดยจะมีการนับหน่วยเวลาทั้งสามแบบคือ real time, user time และ system time

/*
 * =========================================================================
 * C Code for fork() creation test
 * =========================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#define NFORKS 50000

void do_nothing() {
	int i;
	i = 0;
}

int main(int argc, char *argv[]) {
	int pid, j, status;

	for (j = 0; j < NFORKS; j++) {

		/*** error handling ***/
		if ((pid = fork()) < 0) {
			printf("fork failed with error code= %d\n", pid);
			exit(0);
		}

		/*** this is the child of the fork ***/
		else if (pid == 0) {
			do_nothing();
			exit(0);
		}

		/*** this is the parent of the fork ***/
		else {
			waitpid(pid, status, 0);
		}
	}
}

/*
 * =========================================================================
 * C Code for pthread_create() test
 * =========================================================================
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NTHREADS 50000

void* do_nothing(void *null) {
	int i;
	i = 0;
	pthread_exit(NULL);
}

int main(int argc, char *argv[]) {
	int rc, i, j, detachstate;
	pthread_t tid;
	pthread_attr_t attr;

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

	for (j = 0; j < NTHREADS; j++) {
		rc = pthread_create(&tid, &attr, do_nothing, NULL);
		if (rc) {
			printf("ERROR; return code from pthread_create() is %d\n", rc);
			exit(-1);
		}

		/* Wait for the thread */
		rc = pthread_join(tid, NULL);
		if (rc) {
			printf("ERROR; return code from pthread_join() is %d\n", rc);
			exit(-1);
		}
	}

	pthread_attr_destroy(&attr);
	pthread_exit(NULL);

}

ตาราง 5-3 แสดงผลการทดสอบบนหน่วยประมวลผลรุ่นต่างๆ

Ref: https://computing.llnl.gov/tutorials/pthreads/

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