C# ile Spider (Crawler - Web Örümcek) Örneği Bölüm 3: C# İle Çoklu Thread Uygulaması

Oldukça uzun bir aradan sonra tekrar merhaba. Daha önceki paylaşımlarıma binaen kaynak kodları isteyen arkadaşlara, elimden gelen
yardımı yapmaya çalıştım. Son olarak C# ile çoklu thread uygulaması gerçekleştirip bu paylaşıma bir nokta koymak niyetindeyim.
C# ile çoklu thread uygulaması ilginizi çekiyorsa buyrun
burdan devam edelim.

Öncelikle thread ya da Türkçe ifadesiyle kanal nedir biraz bunu anlatmaya çalışayım.
Malumunuz işlemci dediğimiz donanım, bilgisayarımızın en hızlı parçası.
Sabit disk ya da diğer donanımlar şöyle dursun, ram'in bile bu hıza ayak uydurması pek kolay değil.
Bu bilgisayarın doğal sonucudur. Çünkü bilgisayarın bizim yaptığı şey 1 ve
0ları olabildiğince çabuk işlemektir ve bu işlemeyi işlemci, bellek yardımıyla yapmak zorundadır.
Elektrik akımını da biz gözle seçemeyeceğimiz için, bilgisayarımız sanki aynı anda birden fazla iş
yapıyormuş gibi görürüz. Ancak durum gerçekte pek de öyle değil. Daha doğrusu yakın zamana kadar pek öyle değildi.
Son bir kaç yıldır, 2 hatta 4 çekirdekli işlemciler piyasanın tamamına hakim olduğu için bilgisayarlarımız
hakikaten aynı anda bir kaç iş yapmaktadır. Aynı anda bir kaç iş yapsa bile, aynı anda tüm işleri yapması mümkün değildir.
Bir düşünün; msn'de bir arkadaşınızla sohbet ediyorsunuz. Msn ayrı kaynak tüketiyor, klavyede yazı yazmanız lazım,
o ayrı kaynak tüketiyor. Ekran kartının görüntü üretmesi lazım, onlar ayrı kaynak tüketiyor (Gerçi ekran kartlarının
da artık kendi grafik işlemcileri var). Herşey işlemcinin kapısında beklemek zorunda. 4 çekirdeğimiz bile olsa, aynı
anda tüm işlerin en fazla 4 tanesine cevap verebiliriz. İşte işlemci bu işlemlerin hepsini bir sıraya koyar bizim yerimize.
Ancak bazı durumlarda bizim araya bişeyler sıkıştırmamız gerekebilir. İşte tam bu noktada imdadımıza thread yani kanal
yetişmektedir. Bence kanal yerine kaynakçı deselermiş daha güzel olurmuş :). Çünkü yaptığı şey neredeyse tamamen araya kaynak yapmak.
İşlemcide kuyrukta bekleyen işlemler üzerine yapışıp, arada kendi işlerini de yaptırmak. Kaynakçılık gerçek hayatta ne kadar kötü birşey olsa
da, bilgisayar üzerinde bizim için çok iyi bişey. Şöyle ki, programı kanallara böldüğünüz zaman, aynı dosyaları, aynı ağı, aynı kaynakları
asenkron olarak kullanmamıza olanak sağlar. Bu da çok kısa zamanda, çok fazla iş yapmamıza olanak sağlar. Böylece uzun sürecek işlemlerimiz için
beklemeden kısa yoldan işlemlerimizi gerçekleştirebiliriz. Eminim ki birçok kişi ie7+, mozilla 2.0+, chrome gibi masaüstü uygulamalarında
çoklu sekme özelliğini oldukça hoş karşılamıştır. Her tarayıcı için ayrı bir pencere açmaktansa, sekme açmayı yeğlerim şahsen. Bir düşünün bir
sekmenin işini bitirmeden, öteki sekmenin bekleyip durduğunu. Sekme olmasının bi anlamı kalmazdı...

İşin gevezeliğini bir yana bırakıp, koda geçtiğimizde demek istediğimi daha iyi anlayacaksınız. C#, daha doğrusu .Net çatısı bize
çoklu kanal uygulamaları geliştirebilmemiz için iki farklı yöntem sağlamıştır. Birincisi, manuel olarak kanalları kendimiz başlatır,
kendimiz yönetiriz. İkincisi Asenkron sınıfı kullanarak C#'ın bize yardım etmesini sağlarız. Asenkron kullanım biraz daha karmaşık
olduğu için zamanında uygulamayı elle çalıştırdığım kanallar ile gerçekleştirmiştim. Şimdi biraz kod yazmaya başlayalım.

Bu paylaşımın daha önceki iki bölümünde oluşturmaya çalıştığım programı anlatmıştım. Kaldığım yerden devam ederek , hem programı hem de
kanal yapısını anlatmayı düşünüyorum. Programda kullandığım tablolardan, gezinmek üzere kaydettiğim sitelerin listesini çekip, threadleri birer
birer çalıştırmaya başladım:

int i = 0;
string[] tablolar = { "tablo1", "tablo2", "tablo3", "tablo4", "tablo5" };

OleDbCommand komut = new OleDbCommand();
komut.Connection = bag;
OleDbDataReader siteal;
komut.CommandText = "select anasayfa,urunara from siteler where tara=0";
siteal = komut.ExecuteReader();
if (siteal.HasRows == false) return false;

while (siteal.Read())
{
asil = siteal[0].ToString();

OleDbCommand anasayfa = new OleDbCommand();
anasayfa.CommandText = "insert into gelen values ('" + asil + "')";
anasayfa.Connection = bag;
anasayfa.ExecuteNonQuery();

Thread[] kanal = new Thread[4];
for (int t = 0; t < 4; t++)
{
kanal[t] = new Thread(new ThreadStart(basla));
kanal[t].Name = t.ToString();
kanal[t].Start();
}

while (true)
{
int j = 0;
for (int k = 0; k < 4; k++)
{
if (!kanal[k].IsAlive)
j++;
}
if (j == 4) { break; }
Thread.Sleep(2500);
}

string kom = siteal[1].ToString();
OleDbDataAdapter adaptor = new OleDbDataAdapter();
adaptor.SelectCommand = new OleDbCommand(kom, bag);
adaptor.Fill(veritb, tablolar[i]);
i++;
}
return true;
}

Kodun anlamsız olmasına bakmayın, bizi ilgilendiren minik bir parçası :

Thread[] kanal = new Thread[4];
for (int t = 0; t < 4; t++)
{
kanal[t] = new Thread(new ThreadStart(basla));
kanal[t].Name = t.ToString();
kanal[t].Start();
}

while (true)
{
int j = 0;
for (int k = 0; k < 4; k++)
{
if (!kanal[k].IsAlive)
j++;
}
if (j == 4) { break; }
Thread.Sleep(2500);
}


Yapmaya çalıştığımız şey; 4 adet kanal oluşturmak. Sonra da tüm kanallarımızın işinin bitip bitmediğini kontrol etmek.
Bunu da .IsAlive fonksiyonuyla yapmak. Eğer 4 kanalımız da işini bitirdiyse, uygulamamızı çalıştıran ana kanala dönüp,
programın kaldığı yerden devam etmesini sağlamalıyız. Bitirmediyse, ana kanalı 2,5 saniye bekletip tekrar oluşturduğumuz
4 kanalın çalışıp çalışmadığını kontrol ettirmeliyiz.

Burda asıl önemli nokta new Thread(new ThreadStart(basla)) ifadesinin ne işe yaradığıdır. Gerçi ne
anlama geldiği ortada ancak biz yine de bahsetmiş olalım: oluşturduğumuz yeni kanalın nereden başlayacağını işaret etmek için
new ThreadStart() ifadesini kullanmamız yeterli oluyor. Asıl işi yaptığımız yeri açıklayalım biraz da:

Arada yazdığım kodların tamamını buraya aktarmayı düşünmüyorum. Zaten ilgilenenlere kaynak kodun tamamını yolluyorum. Anlatmak
istediğim bir kaç önemli nokta var. Birincisi yukarda bahsettiğimiz aynı anda, aynı dosya, aynı ağ, aynı kaynak kullanımını nasıl
sağlayacağımız. Sonuçta aynı dosyaya aynı anda sadece bir tek kanal erişebilir. Bu durumu şöyle aşıyoruz:

Kullanacağımız kaynak bir veritabanıysa Transaction başlatabiliriz. Ancak ben sadece veritabanı kullanılmayacağını hesaba katarak
Monitor nesnesini kullandım. Bunun yerine 'lock' da kullanılabilirdi. Ancak Monitor daha kullanışlı. Şöyle
ki:

Monitor.Enter(object) fonksiyonuyla birlikte, o an için kilitlemek istediğimzi nesneyi çağırıyoruz. Bu nesne bizim veritabanı bağlantımız
olunca bir nevi transaction gerçekleştirmiş oluyoruz. Yani böylece kaynağı, o anki kanaldan başka kimse kullanamaz demiş oluyoruz.
Monitor.Exit() fonksiyonunu kullandığımızda ise, kilidi kaldırıp kaynağı serbest bırakmış oluyoruz. Bundan başka Monitor.PulseAll()
fonksiyonu da çok kullanışlı bir fonksiyon. Sonuçta bizim tek bir veritabanımız ve 4 ayrı kanalımız var. Veritabanı üzerinde bir kanal
işlem yaparken, diğer kanallar kuyrukta bekleyecektir. PulseAll() dediğimiz zaman, kaynağın artık serbest kaldığını diğer kanallara iletmiş
oluyoruz. İşlemcimiz de ilk sıradaki kanalı işlemeye başlıyor böylece.

Evet, olayımız bu kadar diyebilirim. Bu 4 kanala, tüm linkleri gezdirdikten sonra, bir de linkleri ayrıştırma işini verirsek, programımız
gayet güzel bir şekilde çalışmış olacak. Sonuç olarak temel anlamda çoklu kanal uygulaması geliştirmiş olduk. Daha önceki yazılarımda
belirttiğim gibi, isteyen arkadaşlara kaynak kodları gönderebilirim. Tekrar görüşünceye dek, hoşçakalın...

12 yorum:

Adsız dedi ki...

Hocam kaynak kodlarını gönderebilir misin?
webmaster.41 @ gmail.com
teşekkürler.

Adsız dedi ki...

Yazılarınız gercekten ilgi çekici, emeğiniz için tekrar teşekkurler.

muratyesil17@gmail.com'a yazdıgınız kodları gonderebilir misiniz?

Tekrar teşekkurler.

ceyda dedi ki...

Yazılarınız gerçekten çok işime yarayacak... şimdiden teşekkür ederim bana da kaynak kodları yollayabilir misiniz?

samaratum@hotmail.com

Unknown dedi ki...

Yazıların gerçekten çok yararlı, okulda tam da bununla ilgili bir projem vardı.lütfen kaynak kodları banada iletebilirmisin?Cok işime yarıcaktır.
ozguragcihan@hotmail.com

teşekkürler...

Sinan dedi ki...

merhaba. web crawler 3. bölümün de kaynak kodları gönderirseniz sevinirim. netsinan(at)gmail.com

Adsız dedi ki...

merhaba anlatmış olduğunuz program çok ilgi cekici.Bana kaynak kodları gönderebilirmisiniz? promete83@hotmail.com

furuko dedi ki...

konu gayet güzel kodları gönderirseniz sevinirim...
furukoleo@hotmail.com

Adsız dedi ki...

Öncelikle anlatım tarzınızın gayet anlaşılır ve doyurucu olduğunu söylemek isterim. Bu tarz anlatımlarınızın devamını bekleriz. C# Spider kodlarınızı perseus1981@windowslive.com 'a gönderirseniz sevinirim.

z.burak güven dedi ki...

Kusura bakmayın, askerde olduğum için oldukça geç cevap veriyorum :) şu anda kaynak kodlara ulaşamıyorum ama en kısa zamanda kodları iletmek isterim. İlginiz için teşekkürler, iyi çalışmalar dilerim...

Furkan Tunç dedi ki...

Öncelikle teşekkür ederim.kodları benimlede paylaşırsanız sevinirim...
furkantnc@gmail.com

Adsız dedi ki...

Harika bir yazı dizisi olmuş. Kaynak kodları göndermeniz mümkünmü
msozkan25@hotmail.com

Unknown dedi ki...

bilgiler için çook teşekkür ederim.
eğer bunca zaman sonra kaynak kodları hala elinizde mevcut ise diğer iki konuyla birlikte bana yollayabilir miziniz.
hasslann@gmail.com