Semafor
Peran Hardware Dalam Proses Sinkronisasi
Sebelum adanya berbagai macam teknik sinkronisasi seperti yang ada sekarang,
para programmer sering memanfaatkan fasilitas yang telah diberikan oleh
hardware dari komputer. Sehingga sinkronisasi tidak akan terlepas dari peran
hardware.
Mengapa programmer tidak memakai pendekatan dari sisi software? hal ini
disebabkan karena sulit dan kompleksnya implementasi sinkronisasi yang akan
membawa kepada turunnya performa dari produk yang dibuat.
Processor Synchronous
Central Processing Unit, CPU, mempunyai suatu mekanisme yang dinamakan
interrupt. Di dalam sistim operasi, mekanisme ini digunakan secara intensif, atau
dengan kata lain, banyak konsep-konsep sistim operasi yang menggunakan
mekanisme ini. Sebagai contoh : system call, process scheduling, dsb.
Berbicara mengenai sinkronisasi berarti kita mengasumsikan bahwa akan ada 2
atau lebih proses yang sedang berjalan di komputer secara concurrent, atau dengan
kata lain konsep time-shared sudah diimplementasikan di sistim operasi.
'
'
'
'
'
'
'
yang dinon-aktifkan hanya satu prosesor, hal ini dapat mengakibatkan hal - hal
yang tidak diinginkan.
Memory Synchronous
Dilihat dari nama mekanismenya, maka kita sudah dapat memprediksi bahwa
mekanisme ini akan menggunakan jasa memori. Memang hal tersebut benar,
mekanisme memory synchronous memakai suatu nilai yang disimpan di memori,
dan jikalau suatu proses berhasil mengubah nilai ini, maka proses ini akan
meneruskan ke instruksi selanjutnya, jika tidak, maka proses ini akan berusaha
terus untuk dapat mengubahnya.
Jika dilihat dari paragraf di atas, mekanisme ini lebih cocok dikategorikan sebagai
pendekatan dari software. Tetapi, jika kita perhatikan lebih lanjut, ternyata
mekanisme ini memerlukan jasa hardware. Syarat yang harus dipenuhi agar
mekanisme ini dapat berjalan adalah perlunya hardware mempunyai kemampuan
untuk membuat suatu instruksi dijalankan secara atomic. Pengertian dari
instruksi atomic adalah satu atau sekelompok instruksi yang tidak dapat
diberhentikan sampai instruksi tsb selesai. Detil mengenai hal ini akan dibicarakan
di bagian - bagian selanjutnya.
Sebagai contoh, kita dapat memperhatikan contoh program Java tm yang ada di
bawah ini :
00 boolean testAndSet( boolean variable[] )
01
{
02
boolean t = variable[0];
03
variable[0] = true;
04
return t;
05
}
.....
56 while (testAndSet(lock)) { /* do nothing
57 // Critical Section
58 Lock[0] = false;
59
// Remainder Section
*/ }
dapat dibaca buku - buku programming javatm. Satu catatan di sini adalah, contoh
ini hanyalah sebuah ilustrasi dan tidak dapat dicompile dan dijalankan, karena
Javatmkonsep atomic instruction di Javatm bersifat transparan dari sisi programmer
(akan dijelaskan pada bagian-bagian selanjutnya).
Keunggulan dari memory synchronous adalah pada lingkungan multiprocessor,
semua processor akan terkena dampak ini. Jadi semua proses yang berada di
processor, yang ingin mengakses critical section, meskipun berada di processor
yang berbeda - beda, akan berusaha untuk mengubah nilai yang dimaksud.
Sehingga semua processor akan tersinkronisasi.
Instruksi Atomic
Seperti yang telah dijelaskan pada bagian sebelumnya, instruksi atomic adalah satu
atau sekelompok instruksi yang tidak dapat diberhentikan sampai instruksi tersebut
selesai. Kita telah memakai instruksi ini pada method testAndSet.
Instruksi yang dimaksud di sini adalah instruksi-instruksi pada high-level
programming, bukanlah pada tingkat instruksi mesin yang memang sudah bersifat
atomic. Sebagai contoh : i++ pada suatu bahasa pemrograman akan
diinterpertasikan beberapa instruksi mesin yang bersifat atomic sbb :
00 Load R1,i ' load nilai i ke register 1
01 Inc R1
' tambahkan nilai register 1 dengan angka 1
02 Store i,R1 ' simpan nilai register 1 ke i
instruksi baris 00-02 bersifat atomic , tetapi i++ tidak bersifat atomic, mengapa?
sebagai contoh kasus, katakanlah sekarang processor baru menyelesaikan baris 01,
dan ternyata pada saat tersebut interrupt datang, dan menyebabkan processor
melayani interrupt terlebih dahulu. Hal ini menyebabkan terhentinya instruksi i++
sebelum instruksi ini selesai. Jikalau instruksi ini (i++) bersifat atomic, maka
ketiga instruksi mesin tsb tidak akan diganggu dengan interrupt.
Perlu diketahui bahwa instruksi ini bukanlah seperti pada processor
synchronous yang mana akan mematikan interrupt terlebih dahulu, tetapi instruksi
ini sudah build-in di processor.
Designer processor dapat mengimplementasi konsep ini dengan dua cara yaitu :
1. mengimplementasi instruksi yg build-in
2. mengimplementasi processor mampu membuat suatu instruksi
menjadi atomic
Intel Pentium ternyata memakai cara yang kedua, yaitu dengan adanya suatu
perintah LOCK-Assert. Dengan perintah ini maka semua instruksi dapat dijadikan
atomic. Sedangkan SPARC dan IBM mengimplementasikan suatu rutin yang
bersifat atomic seperti swap dan compareAndSwap.
Semafor
Sejarah Semafor
Telah dikatakan di atas bahwa pada awalnya orang-orang memakai konsep-konsep
sinkronisasi yang sederhana yang didukung oleh hardware, seperti pemakaian
interrupt atau pemakaian rutin-rutin yang mungkin telah diimplementasi oleh
hardware.
Pada tahun 1967, Djikstra mengajukan suatu konsep dimana kita memakai suatu
variable integer untuk menghitung banyaknya proses yang sedang aktif atau yang
sedang tidur. Tipe dari variable ini dinamakan semafor.
Tahun-tahun berikutnya, semafor banyak dipakai sebagai primitif dari mekanisme
sinkronisasi yang lebih tinggi dan kompleks lagi. Sebagai contoh : monitor dari
Javatm. Selain untuk hal tersebut, kebanyakkan semafor juga digunakan untuk
sinkronisasi dalam komunikasi antar device (perangkat keras).
Konsep dan Pengertian Semafor
Konsep semafor yang diajukan oleh Djikstra terdiri dari 2 subrutin yang bernama P
dan V. Nama P dan V berasal dari bahasa Belanda yang berarti Naik dan Turun
atau Wait dan Signal. Untuk pembahasan kali ini, kita akan memakai Wait dan
Signal.
Subrutin wait akan memeriksa apakah nilai dari semafor tersebut di atas 0. Jika ya,
maka nilainya akan dikurangi dan akan melanjutkan operasi berikutnya. Jika tidak
maka proses yang menjalankan wait akan menunggu sampai ada proses lain yang
menjalankan subrutin signal.
Satu hal yang perlu diingat adalah subrutin wait dan signal haruslah bersifat
atomic. Di sini kita lihat betapa besarnya dukungan hardware dalam proses
sinkronisasi.
Nilai awal dari semaphore tersebut menunjukkan berapa banyak proses yang boleh
memasuki critical section dari suatu program. Biasanya untuk mendukung
sifat mutual exclusive , nilai ini diberi 1.
10 void synchronized
waitNonSpinLock( int semaphore [])
11 {
12
while(semaphore[0] <= 0)
13
{
14
wait(); // blocks thread
15
}
16
semaphore[0]--;
17 }
Perbedaan dari kedua subrutin ini adalah terletak pada aksi dari kondisi nilai
semafor kurang atau sama dengan dari 0 (nol). Untuk yang spinlock , kita dapat
melihat proses akan berputar-putar di while baris no 2 (maka itu
disebut spinlock atau menunggu dengan berputar). Sedangkan pada non-spinlock,
proses dengan mudah memanggil perintah wait, setelah itu sistim operasi akan
mengurus mekanisme selanjutnya.
Jangan bingung dengan kata synchronized pada baris 10. Kata ini ada karena
memang konsep dari Javatm, apabila sebuah proses ingin menunggu, maka proses
tersebut harus menunggu di suatu objek. Pembahasan mengenai hal ini sudah
diluar dari konteks buku ini, jadi untuk lebih lanjut silahkan merujuk kepada buku
Javatm pegangan anda.
Subrutin signal
Karena subrutin wait memiliki 2 versi maka hal ini juga berpengaruh kepada
subrutin signal. Subrutin signal akan terdiri dari 2 versi sesuai dengan yang ada di
subrutin wait.
Marilah kita lihat listing programnya
00
01
02
03
10
11
12
13
14
15
16
Letak perbedaan dari kedua subrutin di atas adalah pada notifyAll. NotifyAll
berarti membangunkan semua proses yang sedang berada di waiting queue dan
menunggu semaphore yg disignal.
diperhatikan lebih lanjut anda akan menyadari bahwa akan selalu ada satu
pasang instruksi wait dan signal dari suatu semafor.
perintah wait digunakan sebagai pintu masuk critical section dan perintah signal
sebagai pintu keluarnya. Mengapa semafor dapat dijadikan seperti ini? Hal ini
disebabkan dengan semafor ketiga syarat utama mengenai sinkronisasi dapat
dipenuhi.
Seperti yang telah dijelaskan pada bagian sebelumnya, agar critical section dapat
terselesaikan ada 3 syarat yaitu :
1. Mutual exclusive
2. Make progress
3. Bounded waiting
Sekarang marilah melihat lagi listing program yg ada di bagian sebelumnya
mengenai wait dan signal. Jika nilai awal dari semafor diberikan 1, maka artinya
adalah hanya ada satu proses yang akan dapat melewati pasangan wait-signal.
Proses-proses yang lainnya akan menunggu. Dengan kata lain, mekanisme
semaphore dengan policy nilai diberikan 1, dapat menjamin syarat yang
pertama, mutual exclusive.
Bagaimana dengan syarat yang kedua, make progress? Sebenarnya pada waktu
proses yang sedang berada di dalam critical section keluar dari bagian tersebut
dengan memanggil signal, proses tersebut tidak memberikan akses ke critical
section kepada proses tertentu yang sedang menunggu
tetapi, membuka kesempatan bagi proses lain untuk berkompetisi untuk
mendapatkannya. Lalu bagaimana jika ada 2 proses yang sedang menunggu dan
saling mengalah? mekanisme semafor memungkinkan salah satu pasti ada yang
masuk, yaitu yang pertama kali yang berhasil mengurangi nilai semaphore menjadi
0. Jadi di sini semafor juga berperan dalam memenuhi syarat kedua.
Untuk syarat yang ketiga, jelas tampak bahwa semafor didefinisikan sebagai
pasangan wait-signal. Dengan kata lain, setelah wait, pasti ada signal. Jadi proses
yang sedang menunggu pasti akan mendapat giliran, yaitu pada saat proses yang
sedang berada di critical section memanggil signal.
Contoh suatu potongan program critical section yang memakai semaphore dapat
dilihat di bawah ini. Catatan bahwa program di bawah ini hanyalah pseudo code.
00 wait(semaphoreVar)
01 // critical section
02 signal(semaphoreVar)
17
18
Proses 2
wait(semaphoreVar)
print "dua"
siapapun yang berjalan lebih cepat, maka keluarannya pasti "satu" baru "dua". Hal
ini disebabkan karena jika proses 2 jalan terlebih dahulu, maka dia akan menunggu
(nilai semafor adalah 0) sampai proses 1 memanggil signal. Sebaliknya jika proses
1 jalan terlebih dahulu, dia akan memanggil signal untuk memberikan jalan kepada
proses 2.
Solusi Pembuatan Counting Semaphore dari Binary Semaphore
Pembuatan counting semaphore banyak dilakukan para programmer untuk
memenuhi alat sinkronisasi yang sesuai dengannya. Seperti yang telah dibahas di
atas, bahwa counting semaphore ada beberapa macam. Pada bagian ini, akan
dibahas counting semaphore yang memperbolehkan harga negatif.
Listing program di bawah ini diambil dari buku silberschatz.
00 binary-semaphore S1,S2;
01 int C;
wait (S1);
C--;
if ( C < 0 ) {
signal (S1);
wait (S2);
}
signal (S1);
wait (S1);
C++;
if (C <= 0)
signal (S2);
else
signal (S1);
Kita memerlukan 2 binary semaphore pada kasus ini, maka pada baris 00
didefinisikan dua binary semaphore. Baris 01 mendefinisikan nilai dari semafor
tersebut. Perlu diketahui di sini bahwa waitC adalah wait untuk counting
semaphore, sedangkan wait adalah untuk binary semaphore.
Jika diperhatikan pada subrutin waitC dan signalC di awal dan akhir diberikan
pasangan wait dan signal dari binary semaphore. Fungsi dari binary
semaphore yang ini adalah untuk menjamin critical section(instruksi wait dan
signal dari semafor bersifat atomic, maka begitu pula untuk waitC dan signalC,
jadi kegunaan lain semafor adalah untuk membuat suatu subrutin menjadi
bersifat atomic).
Binary semaphore S2 sendiri digunakan sebagai tempat menunggu giliran prosesproses. Cara menunggu proses - proses tersebut bisa spinlock atau nonspinlock tergantung dari implementasi binary semaphore yang ada.
Perhatikan baris 03 dan 04. Baris ini berbeda dengan apa yang sudah dijabarkan
pada bagian sebelumnya. Karena baris ini maka memungkinkan nilai semafor
untuk menjadi negatif. Lalu apa artinya bagi kita? Ternyata nilai negatif
mengandung informasi tambahan yang cukup berarti bagi kita yaitu bila nilai
semafor negatif, maka absolut dari nilai tersebut menunjukkan banyaknya proses
yang sedang menunggu atau wait. Jadi arti baris 11 menyatakan bahwa bila ada
proses yang menunggu maka bangunkan mereka semua untuk berkompetisi.
Mengapa pada baris 05 dilakukan signal untuk S1? Alasannya karena seperti yang
telah kita ketahui bahwa semaphore menjamin ketiga sifat dari critical section.
Tetapi adalah tidak relevan bila pada saat waktu menunggu waitC masih
mempertahankan mutual exclusivenya. Bila hal ini terjadi, proses lain tidak akan
dapat masuk, sedangkan proses yang didalamnya menunggu proses yang lain untuk
signal. Dengan kata lain deadlock terjadi. Jadi, baris 05 perlu dilakukan untuk
mengeliminir sifat mutual exclusive pada saat suatu proses menunggu.
Pada baris 12 hanya menyatakan signal untuk S2 saja. Hal ini bukanlah merupakan
suatu masalah, karena jika signal S2 dipanggil, maka pasti ada proses yang
menunggu akan masuk dan meneruskan ke instruksi 07 dan akhirnya 08 di mana
dia memanggil signal S1 yang akan mewakili kebutuhan di baris 12.
CountingSemaphore.java
public class CountingSemaphore
{
public CountingSemaphore( int now )
{
semaphore = now;
mutex = new
BinarySemaphore();
S1 = new BinarySemaphore(0);
}
mutex;
S1;
Consumer.java
public class Consumer extends Thread
{
public Consumer (BinarySemaphore mutex,
CountingSemaphore full,CountingSemaphore empty,
SharedBuffer buff)
{
this.mutex = mutex;
this.full = full;
this.empty = empty;
this.buff = buff;
}
public void run()
{
try
{
while(true)
{
full.waitS();
System.out.print("There's a buffer:");
mutex.waitS();
System.out.println(buff.getBuffer());
mutex.signalS();
empty.signalS();
sleep(1000);
}
}catch (InterruptedException ie)
{}
}
private
private
private
private
BinarySemaphore mutex;
CountingSemaphore full;
CountingSemaphore empty;
SharedBuffer buff;
Producer.java
public class Producer extends Thread
{
public Producer(BinarySemaphore mutex,
CountingSemaphore full,CountingSemaphore
empty,SharedBuffer buff)
{
this.mutex = mutex;
this.full = full;
this.empty = empty;
this.buff = buff;
}
public void run()
{
try
{
while(true)
{
empty.waitS();
Integer random =
new Integer(
(int)(Math.random() * 10));
System.out.println(
"producer add : " + random);
mutex.waitS();
buff.addBuffer(random);
mutex.signalS();
full.signalS();
sleep(1);
}
}catch (InterruptedException ie)
{}
}
private
private
private
private
}
BinarySemaphore mutex;
CountingSemaphore full;
CountingSemaphore empty;
SharedBuffer buff;
SharedBuffer.java
class SharedBuffer
{
public SharedBuffer(int max)
{
buffer = new Object[max];
this.max = max;
i = 0;
j = 0;
}
public void addBuffer(Object buff)
{
buffer[i] = buff;
i = (i + 1) % max;
}
public Object getBuffer()
{
j = (j + 1) % max;
return buffer[j];
}
private Object buffer[];
private int max;
private int i;
private int j;
`}
public class ConsumerProducerTest
{
public static void main(String args[])
{
BinarySemaphore mutex = new BinarySemaphore();
CountingSemaphore full = new CountingSemaphore(0);
CountingSemaphore empty = new
CountingSemaphore(MAX);
SharedBuffer buff = new SharedBuffer(MAX);
Consumer consumer = new Consumer(
mutex,full,empty,buff);
Producer producer = new Producer(
mutex,full,empty,buff);
producer.start();
consumer.start();
}
public static final int MAX = 25;
}
Seperti halnya Dinning Philosopher problem, masalah ini juga akan dibahas lebih
detil pada bagian berikutnya.
Semafor dalam masalah Dining Philosophers
Karena latar belakang dan konsep dari permasalah ini sudah dijelaskan, maka pada
bagian ini hanya akan dijelaskan bagaimana menggunakan semaphore untuk
menyelesaikan masalah ini.
Untuk pembahasan lebih rinci dapat dilihat pada bagian selanjutnya.