Multithreading non preemptive

9.4 Multithreading non preemptive

Seperti yang kita lihat sebelumnya,coroutine adalah semacam kolaborasi multithreading. Setiap coroutine sama dengan thread. Sepasang yield-resume mengganti aturan dari satu thread ke thread yang lain. Tetapi,tidak seperti multithreading yang sebenarnya,coroutine adalah non preemptive. Ketika sebuah coroutine berjalan,ini tidak bisa dihentikan dari luar. Ini hanya menunda Seperti yang kita lihat sebelumnya,coroutine adalah semacam kolaborasi multithreading. Setiap coroutine sama dengan thread. Sepasang yield-resume mengganti aturan dari satu thread ke thread yang lain. Tetapi,tidak seperti multithreading yang sebenarnya,coroutine adalah non preemptive. Ketika sebuah coroutine berjalan,ini tidak bisa dihentikan dari luar. Ini hanya menunda

Tetapi,dengan multithreading non preemptive,bilamana beberapa thread memanggil sebuah operasi yang diblok,seluruh program dihentikan hingga operasi selesai. Untuk kebanyakan aplikasi,ini kebiasaan yang tidak bisa diterima,banyak programmer yang tidak memperhatikan coroutine sebagai sebuah alternative nyata untuk multithreading tradisional. Seperti yang akan kita lihat disini masalah yang menarik.

Disini kita melihat situasi multithreading: kita ingin mengambil beberapa remote file melalui HTTP. Tentu saja,untuk mengambil beberapa remote file,kita mesti tahu bagaimana mengambil satu remote file. Pada contoh ini,kita akan menggunakan pustaka LuaSocket,yang dibuat oleh Diego Nehab. Untuk mengambil sebuah file,kita harus membuka sebuah sambungan ke halaman tersebut,mengirim permintaan file,menerima file dan menutup hubungan. Dalam Lua,kita dapat menulis pekerjaan ini seperti dibawah. Pertama kita panggil pustaka LuaSocket:

require "luasocket"

Kemudian,kita definisikan host dan file yang akan kita ambil. Pada contoh ini,kita akan mengambil referensi spesifikasi HTML 3.2 dari world wide web consortium site

host = "www.w3.org" file = "/TR/REC-html32.html"

Kemudian kita membuka sebuah koneksi TCP ke port 80 dari suatu file:

c = assert(socket.connect(host, 80))

Operasi mengembalikan sebuah objek koneksi,yang mana kita gunakan untuk mengirim permintaan file:

c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")

Metode penerimaan selalu mengembalikan sebuah string yang akan kita baca ditambah string lain dengan status operasi. Ketika host menutup hubungan kita selesai menerima file. Terakhir,kita tutup koneksi:

c:close()

Sekarang kita sudah tahu bagaimana mengambil sebuah file,masalahnya sekarang bagaimana mengambil beberapa file pendekatannya sam dengan mengambil satu file. Tetapi,ini pendekatan berurutan,dimana kita hanya mulai membaca sebuah file setelah selesai membaca yang sebelumnya,ini terlau lambat. Ketika membaca remote file,sebuah program membuang banyak waktu untuk menunggu data diterima. Lebih spesifik,ini membuang banyak waktu ketika memanggil receive. Jadi,program dapat jalan lebih cepat jika mengambil seluruh file secara simultan. Kemudian,ketika sebuah koneksi tidak mempunyai data yang berguna,program dapat membaca dari koneksi yang lain. Nyatanya,coroutines memberikan jalur yang menyenangkan untuk membuat download menjadi simultan. Kita buat sebuah thread baru untuk setiap pekerjaaan Sekarang kita sudah tahu bagaimana mengambil sebuah file,masalahnya sekarang bagaimana mengambil beberapa file pendekatannya sam dengan mengambil satu file. Tetapi,ini pendekatan berurutan,dimana kita hanya mulai membaca sebuah file setelah selesai membaca yang sebelumnya,ini terlau lambat. Ketika membaca remote file,sebuah program membuang banyak waktu untuk menunggu data diterima. Lebih spesifik,ini membuang banyak waktu ketika memanggil receive. Jadi,program dapat jalan lebih cepat jika mengambil seluruh file secara simultan. Kemudian,ketika sebuah koneksi tidak mempunyai data yang berguna,program dapat membaca dari koneksi yang lain. Nyatanya,coroutines memberikan jalur yang menyenangkan untuk membuat download menjadi simultan. Kita buat sebuah thread baru untuk setiap pekerjaaan

Untuk menulis program dengan coroutines,pertama tuliskan kode download sebelumnya sebagai sebuah fungsi:

function download (host, file) local c = assert(socket.connect(host, 80)) local count = 0 -- counts number of bytes read c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") while true do

local s, status = receive(c) count = count + string.len(s) if status == "closed" then break end end

c:close() print(file, count)

end

Karena kita tidak tertarik dengan isi remote file,fungsi ini hanya menghitung ukuran file,menulis file ke standar output. Dalam kode yang baru,kita menggunakan sebuah fungsi bantuan untuk menerima data dari koneksi. Dengan pendekatan terurut,kode akan seperti dibawah ini:

function receive (connection) return connection:receive(2^10) end

Untuk implementasi bersama,fungsi ini harus menerima data tanpa dilarang. Jika tidak cukup waktu untuk mendapatkan data yang berguna,program kembali ke yields. Kode baru seperti ini:

function receive (connection) connection:timeout(0) -- do not block local s, status = connection:receive(2^10) if status == "timeout" then

coroutine.yield(connection)

end return s, status

end

Pemanggilan timeout(0) membuat operasi koneksi tidak bisa dilarang. Ketika status operasi adalah “timeout”,ini artinya operasi yang dilakukan tidak sempurna pada kasus ini, thread kembali ke posisi yields. Argumen yang benar diberikan sinyal yield untuk mendispatcher thread yang masih bekerja. Perlu diingat,dalam keadaan timeout,koneksi selalu mengembalikan data yang telah dibaca hingga timeout,jadi receive selalu mengembalikan S kepada pemanggilnya.

Fungsi selanjutnya menjamin setiap download berjalan dalam sebuah threadnya masing- masing:

threads = {} -- list of all live threads function get (host, file)

-- create coroutine local co = coroutine.create(function ()

download(host, file)

end)

-- insert it in the list table.insert(threads, co) end

Tabel threads melindungi daftar seluruh thread yang hidup,untuk dispatcher. Logika dispatcher sederhana. Umumnya melooping seluruh threads,dengan memanggil satu

per satu. Ini juga harus menghapus daftar thread yang sudah selesai melakukan pekerjaan. Ini akan berhenti ketika tidak ada lagi threads yang berjalan:

function dispatcher () while true do

local n = table.getn(threads) if n == 0 then break end -- no more threads to run for i=1,n do

local status, res = coroutine.resume(threads[i])

if not res then -- thread finished its task?

table.remove(threads, i) break

end end

end end

Terakhir,program utama membuat thread yang dibutuhkan dan memanggil dispatcher. Sebagai contoh,untuk download 4 dokumen dari w3c site,program utamanya seperti dibawah ini:

host = "www.w3.org" get(host, "/TR/html401/html40.txt") get(host,"/TR/2002/REC-xhtml1-20020801/xhtml1.pdf") get(host,"/TR/REC-html32.html") get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-

Core.txt") dispatcher() -- main loop

Mesin saya membutuhkan waktu 6 detik untuk mendownload 4 file menggunakan coroutine. Dengan implementasi sequential, ini mengambil waktu 2 kali lebih lama (15 detik). Kalau dilihat dari kecepatan,implementasi yang terakhir jauh dari optimal. Setiap kali menjalankan proses hanya satu thread yang dibaca. Tetapi, ketika tidak ada thread yang dibaca, dispatcher berada pada posisi sibuk menunggu,bergerak dari satu thread ke thread lain hanya untuk memeriksa apakah mereka masih tidak memiliki data. Sebagai hasilnya,implementasi coroutine menggunakan 30 kali lebih lama dari solusi terurut.

Untuk menghindari kejadian ini,kita dapat menggunakan fungsi select dari LuaSocket. Ini membolehkan sebuah program untuk diblok ketika menunggu perubahan status dalam kelompok socket. Perubahan implementasi ini kecil,kita hanya merubah dispatcher. Versi terbaru seperti ini:

function dispatcher () while true do

local n = table.getn(threads) if n == 0 then break end -- no more threads to run local connections = {} for i=1,n do

local status, res = coroutine.resume(threads[i])

if not res then -- thread finished its task? if not res then -- thread finished its task?

else -- timeout

table.insert(connections, res)

end end if table.getn(connections) == n then

socket.select(connections) end

end end

Selama inner loop, dispatcher yang baru mengumpulkan koneksi yang timed-out dalam tabel koneksi. Perlu diingat,bahwa receive memberikan beberapa koneksi ke yield; resume mengembalikan mereka; ketika seluruh koneksi timeout, dispatcher memanggil select untuk menunggu setiap koneksi berubah status. Implementasi yang terakhir berjalan sama cepat dengan implementasi yang pertama dengan coroutine. Dan lagi,ini tidak ada waktu sibuk menunggu,ini hanya butuh lebih sedikit CPU dibandingkan implementasi terurut.

Latihan

1. Tanpa harus menjalan kode, pastikanlah keluaran script berikut .

local function F() local J, K = 0, 1 coroutine.yield(J) coroutine.yield(K) while true do J, K = K, J + K coroutine.yield(K) end end

F = coroutine.wrap(F) for J = 1, 8 do print(F()) end

2. Tuliskan suatu iterator berbasis coroutine bernama JoinPairs yang fungsi memasingkan elemen-

Elemen dari list parallel. Sebagai contoh, loop berikut:

for Name, Number in JoinPairs({“Sally”, “Mary”, “James”}, {12, 32, 7}) do print(Name, Number) end