8.1. Multiprosesor Memori Bersama 8.2. Membangun Kepararelan Khusus Membuat Proses Simultan
Threads
Pengolahan Paralel/KK021239 8/1/51
8. Pemrograman Dengan Memori Bersama
8.3. Data yang Digunakan Bersama
Membuat Data Yang Digunakan Bersama Mengakses Data Yang Digunakan Bersama Konstruksi Bahasa untuk Kepararelan
Analisis Ketergantungan
Data yang Digunakan Bersama dalam Sistem dengan Cache
8.4. Contoh Program
Pemrosesan dalam UNIX Contoh dengan JAVA
Setiap lokasi memori dapat diakses oleh prosesor
manapun.
Sebuah Ruang alamat tunggal terjadi, ini berarti bahwa
setiap lokasi memori diberikan alamat tunggal/unik
diantara area tunggal suatu alamat.
Pengolahan Paralel/KK021239
Sistem Multiprosesor Dengan Memori Bersama
Untuk sejumlah kecil prosesor, arsitektur umumnya
adalah arsitektur bus tunggal :
Gambar 8.1 Shared Memory Multiprocessor
menggunakan Bus Tunggal
•
Menggunakan bahasa pemrograman baru • Memodifikasi bahasa sekuensial yang ada• Menggunakan rutin library dengan bahasa sekuensial
yang ada
• Menggunakan bahasa pemrograman sekuensial yang ada dan perintahkan kompiler paralel untuk
mengkonversinya ke dalam kode yang dapat dieksekusi
secara paralel
• Pemrosesan UNIX
• Threads (Pthreads, Java, …)
Beberapa Alternatif Pemrograman
TABEL 8.1 Beberapa Bahasa Pemrograman Paralel
Membuat Proses Simultan - Konstruksi FORK-JOIN
Membangun Kepararelan Khusus
Gambar 8.2 Konstruksi FORK-JOIN
Sistem UNIX memanggil fork() untuk membuat sebuah proses baru. Proses baru (child process) adalah suatu salinan eksak dari suatu pemanggilan proses kecuali apabila proses baru tersebut mempunyai ID proses unik. Child process mempunyai salinannya sendiri dari
varibel parent- nya. Variabel parent diberikan nilai yang sama seperti variabel original pada saat proses inisialisasi. Proses fork memulai eksekusi pada saat titik fork.
Pemrosesan Heavyweight UNIX
Apabila proses fork tersebut berhasil, fork() return ()kepada child process dan return ID proses dari child process ke parent process.
Proses-proses tersebut digabungkan (join) dengan pemanggilan sistem wait () dan exit(), yang didefinisikan sebagai berikut :
wait(statusp); /*delays caller until signal received or one of its
*/
/*child processes terminates or stops */
exit(status); /*terminates a process. */
Sebuah Child Process dapat dibuat melalui :
pid = fork(); /* fork */
Kode akan dieksekusi oleh child dan parent
if (pid == 0) exit(0); else wait(0); /* join */
pid = fork();
Jika sebuah child untuk mengeksekusi kode yabg berbeda,
dapat menggunakan :
if (pid == 0) {
Kode akan dieksekusi oleh slave
} else {
}
if (pid == 0) exit(0); else wait(0);
Kode akan dieksekusi oleh parent
Threads
Proses “heavyweight”-
benar-benar program terpisah dengan
Variabelnya sendiri, stack, dan
Alokasi memori
Threads - Secara bersama-sama
Membagi ruang memori yang
sama dan variabel global diantara
rutin-rutin
IEEE Portable Operating System Interface, POSIX, section 1003.1 standard
Pthreads:
Gambar 8.4
pthread_create() and pthread_join().
Pthread Barrier
Rutin
pthread_join()
menunggu satu
thread tertentu untuk
mengakhiri/terminasi.
Untuk membuat satu barrier menunggu seluruh threads,
pthread_join()
dapat diulang :
for (i = 0; i < n; i++)
pthread_create(&thread[i], NULL, (void *) slave, (void *) &arg);
for (i = 0; i < n; i++)
pthread_join(thread[i], NULL);
Detached Threads
Dapat saja terjadi bahwa thread tidak terganggu ketika satu thread terminasi dan pada kasus tersebut join tidak diperlukan.
Threads yang tidak di-join disebut sebagai detached threads.
Gambar 8.5 Detached threads.
Urutan Eksekusi Perintah
Pada suatu sistem multiprosesor, instruksi dari suatu proses/ thread dapat saja berselangan waktunya
.
Contoh
Proses 1 Proses 2
Instruction 1.1 Instruction 2.1 Instruction 1.2 Instruction 2.2 Instruction 1.3 Instruction 2.3
Ada beberapa urutan yang memungkinkan, termasuk :
Instruction 1.1
Diasumsikan satu instruksi tidak dapat dibagi ke dalam langkah yang lebih kecil
Jika dua proses mencetak pesan, sebagai contoh, pesan
tersebut dapat muncul dalam urutan yang berbeda
tergantung kepada penjadwalan dari pemanggilan proses
dari rutin pencetakan.
Keadaan terburuk adalah, setiap karakter dari pesan tersebut
dapat berselang jika instruksi mesin dari rutin pencetakan
dapat berselang.
Optimasi Kompiler/Prosesor
Kompiler (atau prosesor) dapat mengurutkan kembali instruksi untuk keperluan optimasisasi.
Contoh
Pernyataan
a = b + 5;
x = y + 4;
dapat dikompilasi untuk mengeksekusi dalam urutan terbalik :
x = y + 4;
a = b + 5;
dan masih benar secara logik.
Akan sangat menguntungkan apabila men-delay pernyataan a =
b + 5 karena beberapa instruksi sebelumnya yang masih/sedang
dieksekusi oleh prosesor memerlukan lebih banyak waktu untuk
menghasilkan nilai untuk b.
Rutin Thread-Safe
Pemanggilan sistem atau rutin libarari disebut sebagai
thread
safe
dapat dipanggil dari thread multipel secara simultan dan
selalu menghasilkan hasil and selalu menghasilkan hasil yang
benar.
Contoh
Standard I/O thread safe (mencetak pesan tanpa mengacaukan
karakternya).
Rutin yang mengakses data bersama/statis memerlukan perhatian
yang khusus agar dapat membuat suatu thread safe.
8/3jml. Slide
Mengakses Data Bersama
Dimisalkan ada 2 proses, dimana satu proses berfungsi untuk
menambah nilai 1 ke dalam item data bersama, x. Diperlukan
pembacaan untuk isi dari lokasi x, hasil perhitungan x + 1 dan
hasil penulisan kembali ke lokasi semula. Dengan 2 proses
melakukan hal tersebut pada saat yang sama, maka didapat :
Instruction
Process 1
Process 2
x = x + 1;
read x
read x
compute x+1
compute x +1
write to x
write to x
Time
Gambar 8.6 Konflik dalam Pengaksesan variabel Bersama
Bagian Kritis
Suatu mekanisme untuk menentukan bahwa hanya satu
proses mengakses resource tertentu pada satu waktu adalah
dengan membentuk bagian dari kode yang terlibat dalam
resource tersebut disebut sebagai Bagian Kritis (
Critical
Sections
) dan mengatur bahwa hanya satu bagian kritis yang
dieksekusi pada satu waktu.
Apabila satu proses telah selesai menyelesaikan bagian
kritisnya, maka proses lain diijinkan untuk memasuki bagian
kritis untuk resource yang sama. Mekanisme tersebut dikenal
sebagai mutual exclusion.
LOCK
adalah mekanisme sederhana untuk memastikan mutual
exclusion dari suatu bagian kritis (critical section)
Suatu lock adalah variabel 1-bit dimana angka 1
mengindikasikan bahwa satu proses telah masuk ke bagian
kritis dan satu () mengindikasikan bahwa tidak ada proses di
dalam bagian kritis.
Suatu lock beroperasi seperti penguncian suatu pintu.
Suatu proses menuju ke suatu “pintu” dari bagian kritis dan
menemukan “pintu” yang terbuka maka diperbolehkan masuk
ke bagian kritis, kemudian mengunci pintunya agar
menhindari proses lain yang ingin masuk. Setelah proses
selesai, maka pintunya dibuka (unlock) dan meninggalkannya.
SPIN LOCK
Contoh
while (lock == 1) do_nothing; /* no operation in while loop */
lock = 1; /* enter critical section */
.
critical section
.
lock = 0; /* leave critical section */
Gambar 8.7 Mengontrol bagian kritis melalui busy waiting.
Deadlock
Deadlock dapat terjadi dengan 2 proses, dimana proses
yang satu memerlukan satu resource yang sedang
digunakan oleh yang lainnya, dan proses ini
memerlukan resource yang digunakan oleh proses
pertama.
Gambar 8.8 Deadlock (deadly embrace).
Dapat saja terjadi suatu dealock yang circular, dimana beberapa proses mempunyai resource yang diinginkan oleh proses lainnya.
Gambar 8.8 Deadlock n-proses
Semaphores
Suatu semaphore,
s
(say), adalah satu bilangan bulat positif
(termasuk nol) yang dioperasikan pada dua operasi yang
diberi nama
p
dan
v
.
Operasi
p
,
p
(s)
menunggu sampai s lebih besar dari 0 (nol) dan kemudian
mengurangi s dengan 1 (satu) dan mengijinkan proses untuk
berlanjut.
Operasi
v
,
v
(s)
menambah s dengan 1 (satu) untuk melepaskan satu proses
yang menunggu (bila memang ada).
Operasi
p
dan
v
dilaksanakan secara individu. Proses yang
di-delay oleh
p
(s) disimpan dalam suatu abeyance sampai
dilepaskan oleh
v
(s) pada semaphore yang sama.
Mutual Exclusion dari Bagian Kritis (
Critical Section
)
: Dapat dicapai dengan satu semaphore yang mempunyai
nilai 0 atau 1 (
binary semaphore
), yang bertindak sebagai
satu variabel
lock,
tetapi operasi p dan v termasuk mekanisme
proses penjadwalan. Semaphore diinisialisasi dengan 1,
mengindikasikan bahwa tidak ada proses di dalam bagian
kritis yang berasosiasi dengan semaphore.
General Semaphores
: Dapat diambil pada nilai positif selain 0 (nol) dan 1 (satu).
Rutin semaphore terdapat untuk proses dalam UNIX. Tetapi
rutin tersebut tidak terdapat dala Ptread.
Monitor
Suatu prosedur yang menyediakan metode untuk mengakses resource bersama. (shared resource).
Data dan operasi yang dapat beroperasi pada suatu data adalah terenkapsulasi ke dalam satu struktur. Membaca dan menulis hanya
dapat dilakukan denganmenggunakan prosedur monitor, dan hanya satu proses dapat menggunakan prosedur pada satu saat.
Satu prosedur monitor dapat diimplementasikan
menggunakan semaphore untuk melindungi entri-nya.
Contoh monitor dengan Java :
Operasi Variabel Kondisi
Wait(cond_var)
Kondisi menunggu terjadiSignal(cond_var)
Sinyal yang kondisinya terjadiStatus(cond_var)
Return jumlah proses menunggu yang terjadi.Operasi wait juga akan melepaskanlock atau semaphoredan dapat digunakan untuk mengijinkan proses lain masuk ke dalam kondisi.
Contoh :
Umpamakan satu atau lebih proses (atau thread) didesain untuk mengambil aksi ketika satu kaunter, x, adalah nol (0). Proses atau thread lain bertanggungjawab untuk mengurangi kaunter.
Rutin dapat berbentuk sebagai berikut :
Konstruksi Bahasa untuk Kepararelan
Data Bersama
Variabel memori bersama dapat dideklarasikan sebagai
bersama (shared), katakan sebagai :
shared int x;
Konstruksi Paralel
:
par
Untuk menspesifikasikan perintah yang simultan :
par {
Konstruksi
forall
Untuk memulai proses sejenis yang serupa secara bersama-sama :
Forall (I=0; I < n; I++) {
Yang menghasilkan proses n, dimana masing-masingnya berisi perintah yang membentuk tubuh dari loop for, s1,s2, …,sm. Setiap proses
mengubakan nilai yang berbeda dari i.
Contoh : forall (i = 0; i < 5; i++) a[i] = 0;
Membersihkan a[0], a[1], a[2], a[3], dan a[4] menjadi nol secara simultan
.
Analisis Ketergantungan
Untuk mengidentifikasi proses mana yang dapat dieksekusi secara bersama-sama :
Contoh :
Forall (i=0; i < 5; i++) a[i] = 0;
Topi setiap instant dari tubuh adalah independen dari instant lain dan semua instant dapat dieksekusi secar simultan.
Bagaimanapun juga hal tersebut tidak begitu jelas. Memerlukan cara algoritma dalam mengenalkan ketergantunga untuk kompiler paralel.
Kondisi Bernstein
Himpunan kondisi yang cukup untuk menentukan apakah 2 proses dapat dieksekusi secara simultan.
Mari kita mendefinisikan 2 buah himpunan berikut ini :
Ii adalah himpunan dari lokasi memori yang dibaca oleh proses Pi. Oj adalah himpunan dari lokasi memori yang dimasuki oleh proses Pj.
Untuk dua proses
P
1dan
P
2yang akan dieksekusi secara simultan
input untuk proses
P
1harus
bukan bagian dari output dari
P
2dan
input dari P
2harus
bukan bagian dari output dari
P
1misalnya :
Dimana adalah himpunan kosong. Himpunan output dari tiap
proses harus berbeda. Misalnya :
Jika tiga kondisi terpenuhi, dua proses dapat dieksekusi secara simultan
Contoh :
Misalkan 2 perintah (dalam C) berikut ini :
a = x + y;
b = x + z;
Kita mempunyai :
I
1= (x, y)
O
1= (a)
I
2= (x, z)
O
2= (b)
Dan kondisi :
Adalah terpenuhi. Maka perintah a = x+y dan b =x+z dapat
dieksekusi secara simultan.
Data Bersama dalam Sistem dengan Cache
Semua komputer modern mempunyai memori cache, memori
berkecepatan tinggi sangat lekat dengan prosesor untuk menangani data referansi dan kode terkini.
Protokol Koheren Cache
Dalam kebijaksanaan update, salinan data dalam semua cache dimodifikasi pada saat satu salinan masuk.
Dalam kebijaksanaan invalidate, ketika satu salinan data dimasukan, data yang sama pada cache lain di-invalidate (melalui pe-reset-an kembali satu bit yang valid dalam cache). Salinan tersebut hanya dimodifikasi ketika prosesor yang berasosiasi membuat referensi untuk itu.
False Sharing
Bagian yang berbeda dari blok dibutuhkan oleh prosesor yang berbeda tetapi byte-nya tidak sama.
Jika satu prosesor menulis satu bagian dari blok, salinan dari blok komplit dalam cache berbeda harus sudah
dimodifikasi atau di -invalidate-melalui data yang aktual tidak dapat dibagi bersama.
Solusi untuk False Sharing
Kompiler memasuki layout dari data yang disimpan dalam memori utama, memisahkan data hanya yang dimasukan oleh satu prosesor ke dalam blok yang berbeda.
Contoh Program
Untuk menjumlahkan elemen dari suatu array,
a[1000]
:
int sum, a[1000];
sum = 0;
for (i = 0; i < 1000; i++)
sum = sum + a[i];
Proses UNIX
Kalkulasi akan dibagi menjadi dua bagian, satu melakukan i genap dan yang satu lagi melakukan i ganjil, misalnya :
Process 1 Process 2
sum1 = 0; sum2 = 0;
for (i = 0; i < 1000; i = i + 2) for (i = 1; i < 1000; i = i + 2)
sum1 = sum1 + a[i]; sum2 = sum2 + a[i];
Tiap proses akan menambah hasilnya (sum1 atau sum2) ke satu akumulai hasil,
sum :
sum = sum + sum1; sum = sum + sum2;
Gambar 8.10 Lokasi Memori Bersama untuk contoh program UNIX
#include <sys/types.h>
#define array_size 1000 /* no of elements in shared memory */ extern char *shmat();
void P(int *s); void V(int *s); int main() {
int shmid, s, pid; /* shared memory, semaphore, proc id */
char *shm; /*shared mem. addr returned by shmat()*/
int *a, *addr, *sum; /* shared data variables*/
int partial_sum; /* partial sum of each process */ int i;
/* initialize semaphore set */ int init_sem_value = 1;
s = semget(IPC_PRIVATE, 1, (0600 | IPC_CREAT))
if (s == -1) { /* if unsuccessful*/
perror("semget"); exit(1);
}
if (semctl(s, 0, SETVAL, init_sem_value) < 0) { perror("semctl");
exit(1);
}
/* create segment*/
shmid = shmget(IPC_PRIVATE,(array_size*sizeof(int)+1), (IPC_CREAT|0600));
if (shmid == -1) { perror("shmget"); exit(1);
}
/* map segment to process data space */ shm = shmat(shmid, NULL, 0);
/* returns address as a character*/ if (shm == (char*)-1) {
perror("shmat"); exit(1);
}
addr = (int*)shm; /* starting address */ sum = addr; /* accumulating sum */ addr++;
a = addr; /* array of numbers, a[] */ *sum = 0;
for (i = 0; i < array_size; i++) /* load array with numbers */ *(a + i) = i+1;
pid = fork(); /* create child process */ if (pid == 0) { /* child does this */ partial_sum = 0;
for (i = 0; i < array_size; i = i + 2) partial_sum += *(a + i);
else { /* parent does this */ partial_sum = 0;
for (i = 1; i < array_size; i = i + 2) partial_sum += *(a + i);
}
P(&s); /* for each process, add partial sum */ *sum += partial_sum;
V(&s);
printf("\nprocess pid = %d, partial sum = %d\n", pid, partial_sum); if (pid == 0) exit(0); else wait(0); /* terminate child proc */
printf("\nThe sum of 1 to %i is %d\n", array_size, *sum); /* remove semaphore */
if (semctl(s, 0, IPC_RMID, 1) == -1) { perror("semctl");
exit(1); }
/* remove shared memory */
if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("shmctl");
exit(1); }
} /* end of main */
void P(int *s) /* P(s) routine*/ {
struct sembuf sembuffer, *sops; sops = &sembuffer;
sops->sem_num = 0; sops->sem_op = -1; sops->sem_flg = 0;
if (semop(*s, sops, 1) < 0) {
struct sembuf sembuffer, *sops; sops = &sembuffer;
sops->sem_num = 0; sops->sem_op = 1; sops->sem_flg = 0;
if (semop(*s, sops, 1) <0) {
SAMPLE OUTPUT
process pid = 0, partial sum = 250000
process pid = 26127, partial sum = 250500 The sum of 1 to 1000 is 500500
Contoh JAVA
public class Adder {
public int[] array; private int sum = 0; private int index = 0;
private int number_of_threads = 10; private int threads_quit;
public Adder() {
threads_quit = 0;
array = new int[1000]; initializeArray();
startThreads(); }
public synchronized int getNextIndex() {
if(index < 1000) return(index++); else return(-1); }
public synchronized void addPartialSum(int partial_sum) {
sum = sum + partial_sum;
if(++threads_quit == number_of_threads)
System.out.println("The sum of the numbers is " + sum); }
private void initializeArray() {
int i;
for(i = 0;i < 1000;i++) array[i] = i; }
public void startThreads() {
int i = 0;
for(i = 0;i < 10;i++) {
AdderThread at = new AdderThread(this,i); at.start();
public static void main(String args[]) class AdderThread extends Thread {
int partial_sum = 0; Adder parent;
int number;
public AdderThread(Adder parent,int number) {
this.parent = parent; this.number = number; }
public void run() {
int index = 0;
while(index != -1) {
partial_sum = partial_sum + parent.array[index]; index = parent.getNextIndex();
}
System.out.println("Partial sum from thread " + number + " is " + partial_sum);
parent.addPartialSum(partial_sum); }
}