1 Şubat 2020 Cumartesi

asp.net core mvc frameworkde cookie authentication ve authorization

dotnet core 3.1 sürümü ile

Asp.Net Core MVC mimarisinde Cookie tabanlı authentication mekanizması için gerekenler:


Öncelikle Startup.cs dosyasından başlayalım.

ConfigureService methodunda aşağıdaki gibi Authentication ve Authorization bağımlılıklarını IOC ile gönderelim.

        public void ConfigureServices(IServiceCollection services)
        {
            
            services.AddDistributedMemoryCache(); //TODO
            services.AddSession(opt => opt.IdleTimeout = TimeSpan.FromMinutes(10));  //TODO

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, 
                opt => 
                    {
                        opt.LoginPath = "/Account/Login";
                        opt.AccessDeniedPath = "/Account/Login";
                        opt.ExpireTimeSpan = TimeSpan.FromMinutes(30);
                        opt.LogoutPath = "/Account/Logout";
                    }); //TODO: 1
            services.AddAuthorization(); //TODO: 2
            services.AddControllersWithViews();
        }
 

Daha sonra Startup.cs dosyasının içinde bulunan Configure methoduna aşağıdaki şekilde Pipeline ları ekliyoruz..



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            
            app.UseCookiePolicy();  //Ekle

            app.UseRouting();

            app.UseSession(); //Ekle

            app.UseAuthentication();  //Ekle
            app.UseAuthorization();


            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }



Aşağıdaki gibi bir Account controller'ı ekliyoruz ve işimiz tamamlandı.Authentication istediğiniz controllerlara [Authorize] attributeunu eklemeyi unutmayın.

Afiyet olsun :) 



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace SimpleCookieAuthenticationAndAuthorization.Controllers
{
    public class AccountController : Controller
    {
        private readonly IHttpContextAccessor iHttpContextAccessor;

        public AccountController(IHttpContextAccessor iHttpContextAccessor)
        {
            this.iHttpContextAccessor = iHttpContextAccessor;
        }
        
        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Login(string username, string password)
        {

            if (username == "bgokalp" && password == "123456")
            {
                HttpContext.Session.Clear();
                List<Claim> userClaims = new List<Claim>()
                {
                    new Claim(ClaimTypes.NameIdentifier, "1"),
                    new Claim(ClaimTypes.Name, "burak"),
                    new Claim(ClaimTypes.Surname, "gökalp"),
                    new Claim(ClaimTypes.Role, "ADMIN"),
                    new Claim(ClaimTypes.Role, "USER")
                };
                iHttpContextAccessor.HttpContext.Session.SetInt32("ID", 1);
                iHttpContextAccessor.HttpContext.Session.SetString("NAME", "Burak Gökalp");
                ClaimsIdentity claimsIdentity = new ClaimsIdentity(userClaims, CookieAuthenticationDefaults.AuthenticationScheme);
                ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties());
                return RedirectToAction("Index", "Home");
            }
            else return RedirectToAction("Index");
            
        }



        public async Task<IActionResult> Logout()
        {
            if(HttpContext.User.Identity.IsAuthenticated)
            {
                iHttpContextAccessor.HttpContext.Session.Clear();
                await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            }
            return RedirectToAction(nameof(AccountController.Login));
        }
    }
}

26 Şubat 2016 Cuma

R Programlama Temelleri

R Programlama Temelleri

Baktım ki ne zamandır blog a bir şeyler eklemiyorum ve ne zamandır bir çok şey eklemişim birikimlerime.  R Programlama dilinin temel yapısını anlatacağım.

R Programlama dili java, c# gibi yorumlayıcı tabanlı bir dildir. Temel hedefi istatistiksel işlemler ve veri analizinde  kullanılması amaçlanmıştır. (veri madenciliği, grafikleme vs.). SPSS, Matlab'ın data analiz bölümleri, Weka vs. ile karşılaştırılabilir. Ayrıca R Programlama dili ile C, Python, C++, .net gibi dillerde yazılmış fonksiyonlarıda çağırabiliyoruz..

Ücretsiz olarak GNU lisansı ile dağıtımı yapıldığı için istediğiniz yerde kullanabilirsiniz. Windows, Linux, Unix için dağıtımı mevcut.

Avantajlarını, son yıllarda popüleritesinin artmasını vs. yazmayacağım. Bunları zaten internette bulabilirsiniz. Türkçe kaynağı arttırmak amaçlı bu yazı dizisini yazacağım. Umarım insanlara faydamız dokunur.

Şu adresten işletim sisteminize uygun olanı indirin ve kurun : https://www.r-project.org/

Kurulumdan sonra programı açtığımızda ilk olarak karşımıza aşağıdaki gibi bir konsol ekranı gelebilir.:


Aşağıda ilk kod testlerimizi görebilirsiniz ("Merhaba Dünya" yazdırmadan olmaz değil mi :) ):

> print("Merhaba Dünya")
[1] "Merhaba Dünya"
> print(33.5 + 11.2/11.2*10)
[1] 43.5

Yukarıkida iki ">" büyüktür göstergeli kodları satır satır yazın. Sonuçların aynı olması gerek.

R programlama dilinin dil yapısına uygun bir şekilde değişken aşağıdaki gibi tanımlanır:

> BenimDegiskenim <- "burakgokalp.com"
> BenimIkinciDegiskenim <- "www.burakgokalp.com"

> print (BenimDegiskenim)
[1] "burakgokalp.com"
> print (BenimIkinciDegiskenim)
[1] "www.burakgokalp.com"


Diğer genel amaçlı dillerden farklı olarak "=" eşitlik işareti haricinde <- işareti ile sağdan sola aktarım gerçekleşebiliyor. Yazdığımız kod topluluklarını bir script haline getirebiliyoruz. Kod topluluğunu bir txt dosyasına yazın.Örneğin kodumuz bu olsun :

BenimDegiskenim <- "Burak Gökalp ile kısa kısa R programlama"
print (BenimDegiskenim)

Daha sonra bu kodlarla dolu txt dosyasını makinanın herhangi bir yerine deneme.r diye ansi karakter kodlaması ile kaydedin. Linux terminalde Rscript deneme.r şeklinde windows'da ise Rscript.exe  ile bu işi yapabilirsiniz. Aşağıda örnek bir R Script örneği denemesi bulunmaktadır:

C:\Program Files\R\R-3.2.3\bin\x64>Rscript deneme.r
[1] "Burak Gökalp ile kısa kısa R programlama"



R Programlama dilinde basit veri tipleri (vectorlerin sınıfları diyede geçer) aşağıdaki gibidir:

  1. Numeric
  2. Integer
  3. Complex
  4. Logical
  5. Character
Bunun haricinde birde raw data vardır ki bunu dahil etmiyorum. Yukarıdaki tipler, vectorlerde sakalnabilecek veri tipleridir.

1. Numeric

Numeric sayı tipi aslında ondalıklı (decimal) sayı tipini temsil eder. Bir kaç örnek test edelim

> numerik <- 32.4
> tamSayi <- 23
> 
> print(class(numerik))
[1] "numeric"
> print(class(tamSayi))
[1] "numeric"

tamSayi değişkenine gerçektende tamsayı aktarmamıza rağmen niye numeric olarak atanmaktadır? Bu sorunun cevabı için integer değişkenlere geçelim

2. Integer

Bir integer değişken oluşturmak için "as.integer()" fonksiyonunu çağırmamız gerek. Aşağıdaki örnekte büyük küçük harf ayrımı yapan R Programlama dili ile iki adet değişken tanımlayıp atama yapıyoruz.

> tamSayi = 30
> tamsayi = as.integer(31)
> tamSayi
[1] 30
> tamsayi
[1] 31
> class(tamSayi)
[1] "numeric"
> class(tamsayi)
[1] "integer"

Görüldüğü gibi tamSayi ile tamsayi arasındaki numeric ve integer tipi farkı sadece atama yapılırken oluşturuluyor. as.integer() fonksiyonu ile istersek character tipindeki tanımlanmış tam sayılarıda alabiliriz.

> tamsayi = as.integer(31.645)
> tamsayi
[1] 31
> #string değerlerdende sayıları alabiliriz örneğin:
> print(as.integer("31.645"))
[1] 31

3. Complex

Sanal değerli (karmaşık sayılar) sayılar için kullanılan bir değişkendir. Aşağıdaki örneklerden temel kullanımları çıkarabilirsiniz:


> z =  1 + 21i    ##karmaşık bir sayı oluşturalım
> z
[1] 1+21i
> class(z)
[1] "complex"
> sqrt(z)
[1] 3.318418+3.164158i
> sqrt(as.complex(-1))
[1] 0+1i

4. Logical

Mantıksal "True" (Doğru) ya da "False" (Yanlış) değerlerini saklamak için kullnılır. Bunun için iki stringi karşılaştırma yaparak sonucunu başka bir değişkene yazdıralım

> isim = "burak"
> isim2 = "Burak"
> sonuc = isim == isim2
> class sonuc
Hata: unexpected symbol in "class sonuc"
> class(sonuc)
[1] "logical"
> sonuc
[1] FALSE
> isim = "Burak"
> sonuc = isim == isim2
> sonuc
[1] TRUE
> class(sonuc)
[1] "logical"

Görüldüğü gibi, isterse as.integer gibi as.logical gibi bir fonksiyonlar değişkenlerin mantıksal değerini alabiliriz. Aşağıdaki gibi bir örnekle bunun kullanımını görebiliriz.

> sonuc = as.logical(21)
> sonuc
[1] TRUE
> sonuc = as.logical(0)
> sonuc
[1] FALSE
> sonuc = as.logical(-3)
> sonuc
[1] TRUE


5. Character

Karakter ve string tipindeki değişkenleri tanımlamak için kullanılır. Aşağıda yaptığım örnekte string birleştirme, tip dönüşümü gibi olduğunu görebilirsiniz.


> x = as.character(3.14)
> x
[1] "3.14"
> class(x)
[1] "character"
> pi = as.numeric(x)
> pi
[1] 3.14
> class(pi)
[1] "numeric"
> # iki adet karakter aşağıdaki karakter ekleme fonksiyonu ile eklenebilir
> print(paste(pi, " ", x))
[1] "3.14   3.14"
> #ANSI C'den aşina olduğumuz sprintf fonksiyonu R Programlama
> #dilindede aynı şekilde kullanabiliyoruz
> sprintf("pi nin tipi %s, x in tipi %s", class(pi), class(x))
[1] "pi nin tipi numeric, x in tipi character"
> 


R Programlama dilinde değişkenler dinamik tiplidirler. Bir ansi C veya java gibi veri tipini belirtmeye gerek yoktur. Bir betik dili olan PHP gibi değişkenlerin tipleri yorumlayıcı tarafından belirlenir. Tip dönüşümlerini el yordamıyla değiştirmek için gerekli fonksiyonları zaten belirttik.


Ne kadar çoğu yerde "data type" veya "r-object" olarak nitelendirilseler de ben aşağıdaki yapılara "R Dilindeki Veri Yapıları" şeklinde tanımlama yapmak istiyorum.

Vector
List
Matrix
Array
Factor

Şimdi bunların kısa kısa neler olduğunu, nasıl tanımlandıklarını ve örnek kullanımlarını açıklayalım.

 

Vector : 

 Vector'ler c() fonksiyonu ile tanımlanırlar. c() fonksiyonu arguman olarak aldığı tüm nesneleri oluşturacağı vector'e ekler.

> vectorBurak = c("b", "u", "r", "a", "k")
> vectorBura
Hata: 'vectorBura' nesnesi bulunamadı
> vectorBurak
[1] "b" "u" "r" "a" "k"
> class(vectorBurak)
[1] "character"

Görüldüğü gibi vectorün sınıf tipi içeriğinde sakladığı verilerin tipi olan "character" olarak gözükmektedir. c() fonksiyonu veya herhangi bir fonksiyonun kullanımı ya da herhangi bir veri tipi hakkında bilgi için konsola help(c), help(as.integer), help(character) vs... yazarak merakınızı giderebilirsiniz.

Şimdi vectorler ile birlikte birkaç işlem yapalım.

> a = c(3, 5, 8)
> b = c(7, 5, 2)
> a
[1] 3 5 8
> b
[1] 7 5 2
> 2*a
[1]  6 10 16
> a
[1] 3 5 8
> a = 2*a
> print(a)
[1]  6 10 16
> class(a)
[1] "numeric"
> a <- a/2
> c = a + b
> print(c)
[1] 10 10 10

Yukarıda a ve b vektörleri oluşturuyoruz. a ve b vektörlerinin içeriklerini kendilerini çağırarak ya da print(a) veya print(b) şeklindede yazdırabiliriz. Ben a ve b yi direk çağırarak içeriklerini yazdırdım. Daha sonra 2*a diyerek, a vektörünün içeriğindeki tüm elementleri 2 ile çarpıp sonucu çağırarak yazdırdım. Daha sonra a'nın içerğini
a = 2*a
a <- a/2
Şeklinde a yı eski haline getirdik. Bu işlemler ne kadar "amaçsız" gibi gözüksede aslında değişkenlerde olan değişiklikleri size göstermek yapmaktayım.

> c = a + b
Yukarıdaki satırda a ve b vektörlerinin her bir elementini toplayarak, toplanmış elementlerden yeni bir vektör oluşturuyoruz. Bunun adınada c dedik. "c" nin içeriğine baktığımızda "a" 'nın
> a
[1] 3 5 8

ve "b" nin
> b
[1] 7 5 2

elementlerinin toplamı olduğu görülmektedir. 
3+7 = 10, 
5+5 = 10,
8+2 = 10

> print(c)
[1] 10 10 10

Bu şekilde toplama işlemi, çarpma işlemi, çıkarma işlemi ve bölme işlemi vektörler arasında yapabiliriz. Ancak vektörlerin uzunlukları (eleman sayıları) farklı olduğunda işler biraz değişmekte. Öncelikle uzun olan vektörün eleman sayısı, kısa olan elemanın eleman sayısının katları olmak zorundadır.
Farklı uzunluktaki vektörler biribiri ile eşleşsin diye, kısa olan vektörün elemanları, boşlukları doldurmak için geri dönüşümlü kullanılır. Aşağıdaki örnek ve açıklama daha açıklayıcı olacaktır sanırım.
> b = c(10, 20, 30)
> a = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
> a + b
[1] 11 22 33 14 25 36 17 28 39
> a * b
[1]  10  40  90  40 100 180  70 160 270
> b / a
[1] 10.000000 10.000000 10.000000  2.500000  4.000000  5.000000  1.428571
[8]  2.500000  3.333333
> a - b
[1]  -9 -18 -27  -6 -15 -24  -3 -12 -21

Yukarıdaki örnekte görüldüğü gibi 3 elemana sahip b vektörü ile 9 elemana sahip a vektörü oluşturulmuştur. 9 sayısı 3 ün tam katıdır...
> b = c(10, 20, 30)
> a = c(1,   2,  3, 4, 5, 6, 7, 8, 9)

Öncelikle toplama işlemi sonucuna bakalım. Toplama işleminde ilk üç eleman toplamı normal bir toplamadan ibarettir.
> a + b
[1] 11 22 33 14 25 36 17 28 39

Yukarıdaki işlem b vektöründeki elemanların 2 kere daha yeniden kullanılmasıyla yapılır. Yani aşağıdaki gibi bir dönüşüme uğradığını farz edebiliriz.

> b = c(10, 20, 30, 10, 20, 30, 10, 20, 30)
> a = c(1,   2,  3,  4,  5,  6,  7,  8,  9)

Altı çizilen yerler yeniden kullanılan yerler olarak görüldüğünde toplam sonucunun sutun sutun alt alta yapıldığını anlayabiliriz:

> a + b
[1] 11 22 33 14 25 36 17 28 39

Diğer aritmetik işlemlerdede bir değişiklik olmayacak ve aynı yapı kullanılacaktır. Yani eksik yerlerde elemanlar tekrar edilerek işleme alınacaktır.

> a * b
[1]  10  40  90  40 100 180  70 160 270
> a - b
[1]  -9 -18 -27  -6 -15 -24  -3 -12 -21


Vectorlerde indexler diğer programlama dillerinin aksine 0 yerine 1 den başlamaktadır. Vektorlerde indexler diğer dillerdeki gibi köşeli parantezler ile kullanılır. a[1] gibi...

Aşağıda yazdığım örneklere bakalım:

> a = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
> a[1]
[1] 1
> a[2]
[1] 2
> a[3]
[1] 3
> a[0]
numeric(0)
> a[-3]
[1] 1 2 4 5 6 7 8 9
> a
[1] 1 2 3 4 5 6 7 8 9
> 

Yukarıda görüldüğü gibi indexler diğer dillerin aksine 0 dan başlamaz. R Programlama dilinde diğer dillerin aksine birde negatif index kullanımıda mümkündür. Negatif index kullanımında, belirtilen değerin mutlak değerindeki eleman ayrıştırılarak o şekilde gösterim sağlanır. Yukarıda görüldüğü gibi a[-3] yazıldığında a vektörünün 3. elemanı hariç tüm elemanları gösterilmektedir..

> a[-3]
[1] 1 2 4 5 6 7 8 9
> a
[1] 1 2 3 4 5 6 7 8 9


Vectorlerde birden fazla elemanıda numeric indexli vectorler ilede çağırabiliriz. Yani dizi içinde belirlediğimiz indexlerdeki elemanları seçerek yeni bir dizi çağırmak gibi düşünülebilir.


> a = c(1, 2, 3, 4, 5, 6, 7, 8, 9)
> a
[1] 1 2 3 4 5 6 7 8 9
> b = a[c(1,4,9)]
> print(b)
[1] 1 4 9

Görüldüğü gibi diğer dillerdeki gibi indexlerde bir değil birden fazla index kullanarak esnekliğimizi arttırabiliriz. Yani vector çağırırken, vectorler yardımıyla index belirliyoruz. İndex için kullandığımız vectörde, index numaraları sıralı olmak zorunda değil ya da tekrar eden numaralarda kullanılabilir.

Örnek aşağıdaki gibidir;

> a[c(3,2,1,3,3)]
[1] 3 2 1 3 3

Ayrıca aralık operatörü ile aşağıdaki gibi bir kullanım ile bu iki index numarası ve arasındaki numaralardaki elemanlarda seçilebilir:

> a[5:2]
[1] 5 4 3 2
> a[2:5]
[1] 2 3 4 5

Aralık için belirlenen sayıların büyükten küçüğe ve küçükten büyüğe olması mümkündür.

Isimlendirilmiş Vectorler:

Bu vektörlere aslında php'de array kullananlar aşinadır.R programlama dilindede aynı şeye benzer bir tanımlama yapabiliriz.
> isimSoyisim <- c("Burak", "Gökalp")
> names(isimSoyisim) <- c("isim", "soyisim")
> isimSoyisim["isim"]
   isim 
"Burak" 
> isimSoyisim["soyisim"]
 soyisim 
"Gökalp" 
> isimSoyisim
    isim  soyisim 
 "Burak" "Gökalp" 

 Görüldüğü gibi names() fonksiyonu ile "isimSoyisim" vektörüne, özel isimlendirilmiş indexler atayabiliyoruz. Bu indexleri isimSoyisim["isim"] şeklinde kullanarak istediğimiz değere erişebiliriz.

Bu bir başlangıç olsun. Dersin devamını en yakın zamanda yazacağım. Kolay gelsin.

27 Kasım 2014 Perşembe

Ubuntu veya Linux Mint dağıtımlarına Java 8 JDK kurulumu

Oracle'ın JAVA 8 sürümünde bir çok yeni özellik bulunmakta. Bu özelliklerden ubuntu veya linux mint gibi dağıtımlarda kullanmak istiyoranız aşağıdaki belirttiğim adımları yapmanız yeterli.

1. Adım JAVA 8 'i kurun (JDK 8)

$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer 

2. Adım JAVA Sürümünü doğrulayın

Java sürümünü başarıyla yükledikten sonra aşağıdaki komut ile java sürümünü doğrulayın.


$ java -version

java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)


3. Adım JAVA ortam değişkenlerini ayarlayın


$ sudo apt-get install oracle-java8-set-default

31 Temmuz 2014 Perşembe

raspberry pi yi bilgisayara doğrudan ethernet ile bağlama

Raspberry pi yi rooter veya switch e bağladığınızdan direk olarak ip adresini bulup bağlantı sağlayabiliyorsunuz. Peki ya cat5 veya cat6 kablo ile raspberry pi yi doğrudan bilgisayara bağlarsak?

İşte bu tür durumda raspberry pi muhtemelen ip alamayacak ve raspberry pi ye bilgisayarınızdan doğrudan bağlanamyacaksınız. Bunu yapabilmeniz için windows bilgisayarınızı otomatik ip veren bir DHCP sunucusuna çevirmeniz lazım. Windowsda bu ICS (internet connection sharing) ile mümkün.

Öncelikle denetim masasından;
Denetim Masası ->Ağ ve Internet -> Ağ Bağlantıları
girin. Daha sonra
Varolan internetinizin bağdaştırıcısına sağ tıklayın.
 Daha sonra paylaşım bölümünden "Diğer ağ kullanıcıları, bu bilgisayarın internet bağlantısı yoluyla bağlansın" kutucuğunu doldurup aşağıdan raspberry pi yi bağladığınız "Local Area Connection" ı seçin.

 Şimdi sıra raspberry pi'nin ipadresini bulmaya geldi. Command Line Prompt'u açın ve
arp -a yazın.
Aşağıdaki resimdeki gibi bir görüntü ortaya çıkacak. İşte bu raspberry pi'nizin ip adresidir.
Artık putty pi raspberry pi ye bağlanabilirsiniz.
Kolay gelsin...

19 Mayıs 2014 Pazartesi

Linux GRUB 2 İşletim Sistemi Seçimi Menüsünü Gizlemek

nano /etc/default/grub

ile açılan dosyadan

GRUB_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT=2
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_DEFAULT=0

Dosyayı kaydedin;

Daha sonra update-grub2 yazarak, grub ayarlarını güncelleyin.

Grub'a hoşgeldiniz ekranında ESC tuşuna basarak menuye tekrar ulaşabilirsiniz.

7 Nisan 2014 Pazartesi

OpenMP matris çarpımı ile ilgili basit bir örnek.

#define X1 4
#define Y1 2
#define X2 2
#define Y2 5

int matris1[X1][Y1], matris2[X2][Y2], sonucMatrisi[X1][Y2];

int getchToInt()
{
    char ch = _getch();
    switch (ch)
    {
    case '0': return 0; break;
    case '1': return 1; break;
    case '2': return 2; break;
    case '3': return 3; break;
    case '4': return 4; break;
    case '5': return 5; break;
    case '6': return 6; break;
    case '7': return 7; break;
    case '8': return 8; break;
    case '9': return 9; break;
    }
}

void MatrisCarp(void)
{
    cout << "Birinci Matris:[" << X1 << "][" << Y1 << "]\n";
    int i, j, k;
    for (i = 0; i < X1; i++)
    {
        for (j = 0; j < Y1; j++)
        {
            cout << ", " << i << j << ":";
            matris1[i][j] = getchToInt(); cout << matris1[i][j];
        }
        cout << "\n";
    }

    cout << "Ikinci Matris:[" << X2 << "][" << Y2 << "]\n";

    for (j = 0; j < X2; j++)
    {
        for (k = 0; k < Y2; k++)
        {
            cout << ", " << j << k << ":";
            matris2[j][k] = getchToInt(); cout << matris2[j][k];
        }
        cout << "\n";
    }
#pragma omp parallel for shared(sonucMatrisi,matris1,matris2)
    for (i = 0; i < X1; i++)
    {
        for (j = 0; j < Y2/*X2*/; j++)
        {
            sonucMatrisi[i][j] = 0;
            for (k = 0; k < Y1; k++)
                #pragma omp atomic
                sonucMatrisi[i][j] += matris1[i][k] * matris2[k][j];
        }
    }

    cout << "Sonuc Matrisi:[" << X1 << "][" << Y2 << "]\n";

    for (i = 0; i < X1; i++)
    {
        for (k = 0; k < Y2; k++)
        {
            cout << ", " << sonucMatrisi[i][k];
        }
        cout << "\n";
    }
}

OpenMP ile Paralelleştirerek Faktoriyel, Permütasyon, Kombinasyon hesabı

Visual Studio 2013 ile yalnızca OpenMP kullanarak Faktoriyel, Permütasyon ve Kombinasyon hesabı yapan program aşağıdadır. Kolay gelsin.
#include <iostream>
#include <conio.h>
#include <omp.h>
#include <pthread.h>
#include <assert.h>

using namespace std;

long long OmpFaktoriyel(short int sayi)
{
    long long sonuc = 1;
    #pragma omp parallel for shared(sayi, sonuc)
    for (int i = 1; i <= sayi; i++)
    {
        #pragma omp atomic
        sonuc *= i;
    }
    return sonuc;
}

float OmpPermutasyon(short int n, short int r)
{
    assert(n >= r);
    long long nFaktoriyel;
    long long rnFaktoriyel;
#pragma omp parallel shared(n, r, nFaktoriyel, rnFaktoriyel)
    {
        #pragma sections
        {
            #pragma section
            {
                nFaktoriyel = OmpFaktoriyel(n);
            }
            #pragma section
            {
                rnFaktoriyel = OmpFaktoriyel(n-r);
            }
        }
    }
    return (float)nFaktoriyel / (float)rnFaktoriyel;
}

float OmpKombinasyon(short int n, short int r)
{
    assert(n >= r);
    long long nFaktoriyel;
    long long rnFaktoriyel;
    long long rFaktoriyel;
#pragma omp parallel shared(n, r, nFaktoriyel, rnFaktoriyel)
    {
    #pragma sections
        {
            #pragma section
            {
                nFaktoriyel = OmpFaktoriyel(n);
            }
            #pragma section
            {
                rnFaktoriyel = OmpFaktoriyel(n - r);
            }
            #pragma section
            {
                rFaktoriyel = OmpFaktoriyel(r);
            }
        }
    }
    return (float)nFaktoriyel / ((float)rnFaktoriyel*rFaktoriyel);
}



int main()
{
    short int sayi = 0;
    short int n, r;
    cout << "\nOpenMP de faktoriyeli hesaplanacak sayiyi giriniz:"; cin >> sayi;
    cout << endl << "\tOpenMP Faktoriyel Sonucu:" << OmpFaktoriyel(sayi);
    cout << "\nOpenMP de Permutasyonu hesaplanacak N ve R sayilarini sirayla giriniz:"; cin >> n >> r;
    cout << endl << "\tOpenMP Permutasyonu Sonucu:" << OmpPermutasyon(n,r);
    cout << "\nOpenMP de Kombinasyonu hesaplanacak N ve R sayilarini sirayla giriniz:"; cin >> n >> r;
    cout << endl << "\tOpenMP Kombinasyon Sonucu:" << OmpKombinasyon(n, r);
    _getch();
    return 0;
}

3 Nisan 2014 Perşembe

Pthreads kütüphanesinde Semaphore kullanımı örneği

Semaphore ne işe yarar dediğimizde aslında MUTEX den bir farkı olmadığını söyleyebilirim. Ancak birkaç küçük farklılıkları var. Semaforlar ASIL kullanım amacı birden fazla kaynağın threadler arasında paylaşımıdır. Örneğin 1 adet harf üreten, 3 adet ise harf tüketen threadlerimiz olsun. Harf üretilmeden tüketim olmaması gerek. Tüketilen harfin tekrar tüketilmemesi gerek. İşte burada semaforlar devreye giriyor. Fazla açıklama yapamayacağım açıkçası üşeniyorum. Kodlara açılama eklemeye çalıştım. Kodları Visual Studio 2013 de denedim. Sorunsuz çalışmakta hepsi... Kodları kendim yazdım. En aşağıdaki ingilizce notlardaki örneklerden ilham alarak yazdım diyebilirim. Umarım anlarsınız. Kolay gelsin.
#include <windows.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h> //pthreads kütüphanesinde bulunuyor

#define BUFF_SIZE 10 // Kaç tane harf üreteyim?
#define TUKETICI_SAYISI 40

short int sonUretilenIndis = 0;
short int sonTuketilenIndis = 0;
char buffer[BUFF_SIZE];
// Üretici semafor
sem_t uretici_mutex;
// Tüketici semafor
sem_t tuketici_mutex;

pthread_mutex_t kilit = PTHREAD_MUTEX_INITIALIZER;

pthread_t ptid, c1tid, c2tid, c3tid;

void* uretici(void* arg)
{
    for (int i = 0; i < BUFF_SIZE; i++)
    {
        sem_wait(&uretici_mutex);    /*uretici_mutex-- ile aynı anlamı taşıyor
                                    sem_wait fonksiyonu içine aldığı sem_t tipi değişkeni yoklar
                                    Eğer 0 dan büyük ise değişkeni bir azaltır ve alt işlemlere devam eder
                                    sem_t tipinin en düşük değeri olan 0 ise, 0 dan büyük oluncaya kadar alt satırı
                                    işletmeyi bekletir.*/
        buffer[sonUretilenIndis++] = (char)((rand() % 26) + 65);
        printf("\n   Ben Uretici:Bir adet harf urettim tuketebilirsiniz.");
        sem_post(&tuketici_mutex);    /*üretim yapıldığı için ilk değeri 0 olan
                                    tuketici_mutex değerini bir arttırıyoruz
                                    ve eğer tuketici_mutex değerini bekleyen bir
                                    thread varsa onun uyanmasını istiyoruz...
                                    Buda tabiki void* tuketici(void* arg)
                                    fonksiyonun içindeki sem_wait(&tuketici_mutex);
                                    komutu oluyor*/
    }
    return NULL;
}

void* tuketici(void* arg)
{
    do{
        sem_wait(&tuketici_mutex);
        pthread_mutex_lock(&kilit);
        printf("\nBen Tuketici:%d, tukettigim indis:%d, harf:%c", *(int*)arg, sonTuketilenIndis, buffer[sonTuketilenIndis]);
        sonTuketilenIndis++;
        pthread_mutex_unlock(&kilit);
    } while (sonTuketilenIndis < BUFF_SIZE);
    pthread_t ben = pthread_self();
    pthread_t* threadler[3] = { &c1tid, &c2tid, &c3tid };
    for (int i = 0; i < 3; i++)        //Diğer threadleri iptal et zira son işlemi ben yaptım, beklemelerine gerek yok.
    {
        if (pthread_equal(*threadler[i], ben) == 0)
        {
            pthread_cancel(*threadler[i]);
            //pthread_detach(*threadler[i]);
        }
    }
    return NULL;
}


int main()
{

    int tidler[3] = { 1, 2, 3 };
    srand(time(NULL));
    sem_init(&uretici_mutex, 0, BUFF_SIZE); //uretici_mutex'inin değerini BUFF_SIZE kadar yapıyoruz
    sem_init(&tuketici_mutex, 0, 0); //tuketici_mutex başlangıç için 0
    if (pthread_create(&ptid, NULL, uretici, NULL)) exit(-1);
    if (pthread_create(&c1tid, NULL, tuketici, (void*)&tidler[0])) exit(-1);
    if (pthread_create(&c2tid, NULL, tuketici, (void*)&tidler[1])) exit(-1);
    if (pthread_create(&c3tid, NULL, tuketici, (void*)&tidler[2])) exit(-1);
    //Semaforları kur
    pthread_join(ptid, NULL);
    printf("\nureticinin isi bitti");
    pthread_join(c1tid, NULL);
    printf("\ntuketici1 isi bitti");
    pthread_join(c2tid, NULL);
    printf("\ntuketici2 isi bitti");
    pthread_join(c3tid, NULL);
    printf("\ntuketici3 isi bitti");
    // Semaforları yoket
    sem_destroy(&uretici_mutex);
    sem_destroy(&tuketici_mutex);
    puts("\n");
    system("pause");
    return 0;
}
Kaynak: Remzi Arpaci

** Semaphores **

Simple locks let us build a number of concurrent programs correctly and
efficiently. Unfortunately, there are some operations that are pretty common
in multithreaded programs that locks alone cannot support (as we will describe
below). 

One person who realized this years ago was Edsger Dijkstra, known among other
things for his famous "shortest paths" algorithm in graph theory [1], an early
polemic on structured programming entitled "Goto Statements Considered
Harmful" [2] (what a great title!), and, in the case we will study here, the
introduction of a powerful and flexible synchronization primitive known as the
*semaphore* [3]. 

In this note, we will first describe the semaphore, and then show how it can
be used to solve a number of important synchronization problems. 

[SEMAPHORE: DEFINITION]

A semaphore is as an object with an integer value that we can manipulate with
two routines (which we will call sem_wait() and sem_post() to follow the POSIX
standard). Because the initial value of the semaphore determines its behavior,
before calling any other routine to interact with the semaphore, we must first
initialize it to some value, as this code below does:

--------------------------------------------------------------------------------
#include <semaphore.h>
sem_t s;
sem_init(&s, 0, 1);
--------------------------------------------------------------------------------
                    [FIGURE: INITIALIZING A SEMAPHORE]

In the figure, we declare a semaphore s and initialize it to the value of 1
(you can ignore the second argument to sem_init() for now; read the man page
for details). 

After a semaphore is initialized, we can call one of two functions to interact
with it, sem_wait() or sem_post() [4]. The behavior of these two functions is
described here:

--------------------------------------------------------------------------------
int sem_wait(sem_t *s) {
    wait until value of semaphore s is greater than 0
    decrement the value of semaphore s by 1
}

int sem_post(sem_t *s) {
    increment the value of semaphore s by 1
    if there are 1 or more threads waiting, wake 1
}
--------------------------------------------------------------------------------
                    [FIGURE: INITIALIZING A SEMAPHORE]

For now, we are not concerned with the implementation of these routines, which
clearly requires some care; with multiple threads calling into sem_wait() and
sem_post(), there is the obvious need for managing these critical sections
with locks and queues similar to how we previously built locks. We will now
focus on how to *use* these primitives; later we may discuss how they are
built. 

A couple of notes. First, we can see that sem_wait() will either return right
away (because the value of the semaphore was 1 or higher when we called
sem_wait()), or it will cause the caller to suspend execution waiting for a
subsequent post. Of course, multiple calling threads may call into sem_wait(),
and thus all be queued waiting to be woken. Once woken, the waiting thread
will then decrement the value of the semaphore and return to the user.

Second, we can see that sem_post() does not ever suspend the caller. Rather,
it simply increments the value of the semaphore and then, if there is a thread
waiting to be woken, wakes 1 of them up. 

You should not worry here about the seeming race conditions possible within
the semaphore; assume that the modifications they make to the state of the
semaphore are all performed atomically.

[BINARY SEMAPHORES, A.K.A. LOCKS]

We are now ready to use a semaphore. Our first use will be one with which we
are already familiar: using a semaphore as a lock. Here is a code snippet:

--------------------------------------------------------------------------------
sem_t m;
sem_init(&m, 0, X); // initialize semaphore to X; what should X be?

sem_wait(&m);
// critical section here
sem_post(&m);
--------------------------------------------------------------------------------
                    [FIGURE: A SEMAPHORE AS A LOCK]

To build a lock, we simply surround the critical section of interest with a
sem_wait()/sem_post() pair. Critical to making this work, though, is the
initial value of the semaphore. What should it be?

If we look back at the definition of the sem_wait() and sem_post() routines
above, we can see that the initial value of the semaphore should be 1. Imagine
the first thread (thread 0) calling sem_wait(); it will first wait for the
value of the semaphore to be greater than 0, which it is (the semaphore's
value is 1). It will thus not wait at all and decrement the value to 0 before
returning to the caller. That thread is now free to enter the critical
section. If no other thread tries to acquire the lock while thread 0 is inside
the critical section, when it calls sem_post(), it will simply restore the
value of the semaphore to 1 (and not wake any waiting thread, because there
are no waiting threads).

The more interesting case arises when thread 0 holds the lock (i.e., it has
called sem_wait() but not yet called sem_post()), and another thread (thread
1, say) tries to enter the critical section by calling sem_wait(). In this
case, thread 1 will find that the value of the semaphore is 0, and thus wait
(putting itself to sleep and relinquishing the processor). When thread 0 runs
again, it will eventually call sem_post(), incrementing the value of the
semaphore back to 1, and then wake the waiting thread 0, which will then be
able to acquire the lock for itself.

In this basic way, we are able to use semaphores as locks. Because the value
of the semaphore simply alternates between 1 and 0, this usage is sometimes
known as a *binary semaphore*. 

[SEMAPHORES AS CONDITION VARIABLES]

Semaphores are also useful when a thread wants to halt its own progress
waiting for something to change. For example, a thread may wish to wait for
a list to become non-empty, so that it can take an element off of the list. 
In this pattern of usage, we often find a thread *waiting* for something to
happen, and a different thread making that something happen and 
then *signaling* that it has indeed happened, thus waking the waiting
thread. Because the waiting thread (or threads, really) is waiting for some
*condition* in the program to change, we are using the semaphore as a
*condition variable*. We will see condition variables again later,
particularly when covering monitors. 

A simple example is as follows. Imagine a thread creates another thread and
then wants to wait for it to complete its execution:

--------------------------------------------------------------------------------
void *
child(void *arg) {
    printf("child\n");
    // signal here: child is done
    return NULL;
}

int
main(int argc, char *argv[]) {
    printf("parent: begin\n");
    pthread_t c;
    Pthread_create(c, NULL, child, NULL);
    // wait here for child
    printf("parent: end\n");
    return 0;
}
--------------------------------------------------------------------------------
                 [FIGURE: PARENT WAITING FOR CHILD]

What we would like to see here is the following output:

--------------------------------------------------------------------------------
parent: begin
child
parent: end
--------------------------------------------------------------------------------
           [FIGURE: OUTPUT FROM PARENT WAITING FOR CHILD]

The question, then, is how to use a semaphore to achieve this effect, and is
it turns out, it is quite simple, as we see here:

--------------------------------------------------------------------------------
sem_t s;

void *
child(void *arg) {
    printf("child\n");
    // signal here: child is done
    sem_post(&s);
    return NULL;
}

int
main(int argc, char *argv[]) {
    sem_init(&s, 0, X); // what should X be?
    printf("parent: begin\n");
    pthread_t c;
    Pthread_create(c, NULL, child, NULL);
    // wait here for child
    sem_wait(&s);
    printf("parent: end\n");
    return 0;
}
--------------------------------------------------------------------------------
             [FIGURE: PARENT WAITING FOR CHILD WITH A SEMAPHORE]

As you can see in the code, the parent simply calls sem_wait() and the child
sem_post() to wait for the condition of the child finishing its execution to
become true. However, this raises the question: what should the initial value
of this semaphore be? (think about it here, instead of reading ahead)

The answer, of course, is that the value of the semaphore should be set to is
the number 0. There are two cases to consider. First, let us assume that the
parent creates the child but the child has not run yet (i.e., it is sitting in
a ready queue but not running). In this case, the parent will call sem_wait()
before the child has called sem_post(), and thus we'd like the parent to wait
for the child to run. The only way this will happen is if the value of the
semaphore is not greater than 0; hence, 0 as the initial value makes
sense. When the child finally runs, it will call sem_post(), incrementing the
value to 1 and waking the parent, which will then return from sem_wait() and
complete the program. 

The second case occurs when the child runs to completion before the parent
gets a chance to call sem_wait(). In this case, the child will first call
sem_post(), thus incrementing the value of the semaphore from 0 to 1. When the
parent then gets a chance to run, it will call sem_wait() and find the value
of the semaphore to be 1; the parent will thus decrement the value and return
from sem_wait() without waiting, also achieving the desired effect.

[THE PRODUCER/CONSUMER (BOUNDED-BUFFER) PROBLEM]

The final problem we will confront in this note is known as the
*producer/consumer* problem, or sometimes as the *bounded buffer* problem,
which was also first posed by Dijkstra [5]. Indeed, it was the
producer/consumer problem that led to the invention of semaphores! [8]

Imagine one or more producer threads and one or more consumer
threads. Producers produce data items and wish to place them in a buffer;
consumers grab data items out of the buffer consume the data in some way. 

This arrangement occurs in many places within real systems. For example, in a
multithread web server, a producer puts HTTP requests into a work queue (i.e.,
the bounded buffer); a thread pool of consumers each take a request out of the
work queue and process the request. Similarly, when you use a piped command in
a UNIX shell, as follows:

prompt> cat notes.txt | wc -l

This example runs two processes concurrently; "cat file" writes the body of
the file "notes.txt" to what it thinks is standard output; instead, however,
the UNIX shell has redirected the output to what is called a UNIX pipe
(created by the *pipe()* system call). The other end of this pipe is connected
to the standard input of the process "wc", which simply counts the number of
lines in the input stream and prints out the result. Thus, the "cat" process
is the producer, and the "wc" process is the consumer. Between them is a
bounded buffer.

Because the bounded buffer is a shared resource, we must of course require
synchronized access to it, lest a race condition arise. To begin to understand
this problem better, let us examine some actual code:

--------------------------------------------------------------------------------
int buffer[MAX];
int fill = 0; 
int use  = 0;

void put(int value) {
    buffer[fill] = value;    // line F1
    fill = (fill + 1) % MAX; // line F2
}

int get() {
    int tmp = buffer[use];   // line G1
    use = (use + 1) % MAX;   // line G2
    return tmp;
}
--------------------------------------------------------------------------------
                     [FIGURE: THE PUT AND GET ROUTINES]

In this example, we assume that the shared buffer *buffer* is just an array of
integers (this could easily be generalized to arbitrary objects, of course),
and that the *fill* and *use* integers are used as indices into the array,
and are used to track where to both put data (fill) and get data (use).

Let us assume in this simple example that we have just two threads, a producer
and a consumer, and that the producer just writes some number of integers into
the buffer which the consumer removes from the buffer and prints:

--------------------------------------------------------------------------------
void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        put(i);
    }
}

void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        int tmp = get();
        printf("%d\n", tmp);
    }
}
--------------------------------------------------------------------------------
                 [FIGURE: THE PRODUCER AND CONSUMER THREADS]

Finally, here is the main body of the program, which simply creates the
two threads and waits for them to finish.

--------------------------------------------------------------------------------
int loops = 0;

int main(int argc, char *argv[]) {
    loops = atoi(argv[1]);

    pthread_t pid, cid;
    Pthread_create(&pid, NULL, producer, NULL); 
    Pthread_create(&cid, NULL, consumer, NULL); 
    Pthread_join(pid, NULL); 
    Pthread_join(cid, NULL); 
    return 0;
}
--------------------------------------------------------------------------------
                       [FIGURE: MAIN BODY OF CODE]

If the program is run with loops = 5, what we'd like to get is the producer
"producing" 0, 1, 2, 3, and 4, and the consumer printing them in that
order. However, without synchronization, we may not get that. For example,
imagine if the consumer thread runs first; it will call get() to get data that
hasn't even been produced yet, and thus not function as desired. Things get
worse when you add multiple producers or consumers, as there could be race
conditions in the update of the use or fill indices. Clearly, something is
missing. 

Our first attempt at solving the problem introduces two semaphores, *empty*
and *full*, which the threads will use to indicate when a buffer entry has
been emptied or filled, respectively. Here is the example code:

--------------------------------------------------------------------------------
sem_t empty;
sem_t full;

void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&empty);           // line P1
        put(i);                     // line P2
        sem_post(&full);            // line P3
    }
}

void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&full);            // line C1
        int tmp = get();            // line C2
        sem_post(&empty);           // line C3
        printf("%d\n", tmp);
    }
}

int main(int argc, char *argv[]) {
    // ...
    sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with...
    sem_init(&full, 0, 0);    // ... and 0 are full
    // ...
}
--------------------------------------------------------------------------------
          [FIGURE: ADDING THE EMPTY AND FULL CONDITION VARIABLES]

In this example, the producer first waits for a buffer to become empty in
order to put data into it, and the consumer similarly waits for a buffer to
become filled before using it. Let us first imagine that MAX=1 (there is only
one buffer in the array), and see if this works.

Imagine again there are two threads, a producer and a consumer. Let us examine
a specific scenario on a single CPU. Assume the consumer gets to run
first. Thus, the consumer will hit line C1 in the figure above, calling
sem_wait(&full). Because full was initialized to the value 0, the call will
block the consumer and wait for another thread to call sem_post() on the
semaphore, as desired.

Let's say the producer then runs. It will hit line P1, calling
sem_wait(&empty). Unlike the consumer, the producer will continue through
this line, because empty was initialized to the value MAX (in this case,
1). Thus, empty will be decremented to 0 and the producer will put a data
value into the first entry of buffer (line P2). The producer will then
continue on to P3 and call sem_post(&full), changing the value of the full
semaphore from 0 to 1 and waking the consumer (e.g., move it from blocked to
ready). 

In this case, one of two things could happen. If the producer continues to
run, it will loop around and hit line P1 again. This time, however, it would
block, as the empty semaphore's value is 0. If the producer instead was
interrupted and the consumer began to run, it would call sem_wait(&full)
(line C1) and find that the buffer was indeed full and thus consume it.
In either case, we achieve the desired behavior.

You can try this same example with more threads (e.g., multiple producers, and
multiple consumers). It should still work, or it is time to go to sleep. 

[A PROBLEM: OFF TO THE RACES]

Let us now imagine that MAX > 1 (say MAX = 10). For this example, let us
assume that there are multiple producers and multiple consumers. We now have a
problem: a *race condition*. Do you see where it occurs? (take some time and
look for it)

If you can't see it, here's a hint: look more closely at the put() and get()
code.

If you still can't see it, I bet you aren't trying. Come on, spend a minute on
it at least.

OK, you win. Imagine two producers both calling into put() at roughly the same
time. Assume producer 1 gets to run first, and just starts to fill the first
buffer entry (fill = 0 @ line F1). Before the producer gets a chance to
increment the fill counter to 1, it is interrupted. Producer 2 starts to run,
and at line F1 it also puts its data into the 0th element of buffer, which
means that the old data there is overwritten! This is a no-no; we don't want
any data generated by a producer to be lost. 

[SOLUTION? ADDING MUTUAL EXCLUSION]

As you can see, what we've forgotten here is *mutual exclusion*. The filling
of a buffer and incrementing of the index into the buffer is a *critical
section*, and thus must be guarded carefully. So let's use our friend the
binary semaphore and add some locks. Here is our first try:

--------------------------------------------------------------------------------
sem_t empty;
sem_t full;
sem_t mutex;

void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&mutex);           // line P0 (NEW LINE)
        sem_wait(&empty);           // line P1
        put(i);                     // line P2
        sem_post(&full);            // line P3
        sem_post(&mutex);           // line P4 (NEW LINE)
    }
}

void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&mutex);           // line C0 (NEW LINE)
        sem_wait(&full);            // line C1
        int tmp = get();            // line C2
        sem_post(&empty);           // line C3
        sem_post(&mutex);           // line C4 (NEW LINE)
        printf("%d\n", tmp);
    }
}

int main(int argc, char *argv[]) {
    // ...
    sem_init(&empty, 0, MAX); // MAX buffers are empty to begin with...
    sem_init(&full, 0, 0);    // ... and 0 are full
    sem_init(&mutex, 0, 1);   // mutex = 1 because it is a lock (NEW LINE)
    // ...
}
--------------------------------------------------------------------------------
            [FIGURE: ADDING MUTUAL EXCLUSION, BUT INCORRECTLY]

Now we've added some locks around the entire put()/get() parts of the code, as
indicated by the NEW LINE comments. That seems like the right idea, but it
also doesn't work. Why? Deadlock

[OH OH, DEADLOCK]

Why does deadlock occur? Take a moment to consider it; try to find a case
where deadlock arises; what sequence of steps must happen for the program to
deadlock? 

OK, now that you figured it out, here is the answer. Imagine two threads, one
producer and one consumer. The consumer gets to run first. It acquires the
mutex (line C0), and then calls sem_wait() on the full semaphore (line C1);
because there is no data yet, this call causes the consumer to block and thus
yield the CPU; importantly, though, the consumer still *holds* the lock. 

A producer then runs. It has data to produce and if it were able to run, it
would be able to wake the consumer thread and all would be
good. Unfortunately, the first thing it does is call sem_wait on the binary
mutex semaphore (line P0). The lock is already held. Hence, the producer is
now stuck waiting too.

There is a simple cycle here. The consumer *holds* the mutex and is *waiting* 
for the someone to signal full. The producer could *signal* full but is
*waiting* for the mutex. Thus, the producer and consumer are each stuck
waiting for each other: a classic deadlock.

[FINALLY, A SOLUTION THAT WORKS]

To solve this problem, we simply must reduce the scope of the lock. Here is
the final working solution:

--------------------------------------------------------------------------------
sem_t empty;
sem_t full;
sem_t mutex;

void *producer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&empty);           // line P1
        sem_wait(&mutex);           // line P1.5 (MOVED THE MUTEX TO HERE ...)
        put(i);                     // line P2
        sem_post(&mutex);           // line P2.5 (... AND TO HERE)
        sem_post(&full);            // line P3
    }
}

void *consumer(void *arg) {
    int i;
    for (i = 0; i < loops; i++) {
        sem_wait(&full);            // line C1
        sem_wait(&mutex);           // line C1.5 (MOVED THE MUTEX TO HERE ...)
        int tmp = get();            // line C2
        sem_post(&mutex);           // line C2.5 (... AND TO HERE)
        sem_post(&empty);           // line C3
        printf("%d\n", tmp);
    }
}
--------------------------------------------------------------------------------
                [FIGURE: ADDING MUTUAL EXCLUSION CORRECTLY]

As you can see, we simply move the mutex acquire and release to be just around
the critical section; the full and empty wait and signal code is left outside.
The result is a simple and working bounded buffer, a commonly-used pattern in
multithreaded programs. Understand it now; use it later. You will thank me for
years to come. Or not.

[A READER-WRITER LOCK]

Another classic problem stems from the desire for a more flexible locking
primitive that admits that different data structure accesses might require
different kinds of locking. For example, imagine a number of concurrent list
operations, including inserts and simple lookups. While inserts change the
state of the list (and thus a traditional critical section makes sense),
inserts simply *read* the data structure; as long as we can guarantee that no
insert is on-going, we can allow many lookups to proceed concurrently. The
special type of lock we will now develop to support this type of operation is
known as a *reader-writer lock*. The code for such a lock is available here:

--------------------------------------------------------------------------------
typedef struct _rwlock_t {
    sem_t writelock;
    sem_t lock;
    int   readers;
} rwlock_t;

void rwlock_init(rwlock_t *lock) {
    readers = 0;
    sem_init(&lock, 0, 1);      // binary semaphore
    sem_init(&writelock, 0, 1); // used to lock out a writer, or all readers
}

void rwlock_acquire_readlock(rwlock_t *lock) {
    sem_wait(&lock);
    readers++;
    if (readers == 1)
        sem_wait(&writelock);
    sem_post(&lock);
}

void rwlock_release_readlock(rwlock_t *lock) {
    sem_wait(&lock);
    readers--;
    if (readers == 0)
        sem_post(&writelock);
    sem_post(&lock);
}

void rwlock_acquire_writelock(rwlock_t *lock) {
    sem_wait(&writelock);
}

void rwlock_release_writelock(rwlock_t *lock) {
    sem_post(&writelock);
}
--------------------------------------------------------------------------------
                 [FIGURE: A SIMPLE READER-WRITER LOCK]

The code is pretty simple. If some thread wants to update the data structure
in question, it should call the pair of operations rwlock_acquire_writelock()
and rwlock_release_writelock(). Internally, these simply use the "writelock"
semaphore to ensure that only a single writer can acquire the lock and thus
enter the critical section to update the data structure in question.

More interesting is the rwlock_acquire_readlock() and
rwlock_release_readlock() pair. When acquiring a read lock, the reader first
acquires "lock" and then increments the "readers" variable to track how many
readers are currently inside the data structure. The important step then taken
within rwlock_acquire_readlock() occurs when the first reader acquires the
lock; in that case, the reader also acquires the write lock by calling
sem_wait() on the "writelock" semaphore, and then finally releasing the "lock"
by calling sem_post(). 

Thus, once a reader has acquired a read lock, more readers will be allowed to
acquire the read lock too; however, any thread that wishes to acquire the
write lock will have to wait until *all* readers are finished; the last one to
exit the critical section will call sem_post() on "writelock" and thus enable
a waiting writer to acquire the lock itself.

This approach works (as desired), but does have some negatives, especially
when it comes to fairness. In particular, it would be relatively easy for
readers to starve writers. More sophisticated solutions to this problem exist;
perhaps you can think of a better implementation? Hint: think about what you
would need to do to prevent more readers from entering the lock once a writer
is waiting.

Finally, it should be noted that reader-writer locks should be used with some
caution. They often add more overhead (especially with more sophisticated
implementations), and thus do not end up speeding up performance as compared
to just using simple and fast locking primitives [7]. Either way, they
showcase once again how we can use semaphores in an interesting and useful
way.

[THE DINING PHILOSOPHERS]

If I weren't lazy, I would write a bit about one of the most famous
concurrency problems posed and solved by Dijkstra, known as the *dining
philosopher's problem* [9]. However, I am lazy. The problem is famous because
it is fun and somewhat intellectually interesting; however, its practical
utility is low. I am a practical kind of guy, and thus have a hard time
motivating the time spent to understand something that is so clearly
academic. Look it up on your own if you are interested.

[SUMMARY]

Semaphores are a powerful and flexible primitive for writing concurrent
programs. Many programmers use them exclusively, shunning simpler locks and
condition variables, due to their great simplicity and utility. 

In this note, we have presented just a few classic problems and solutions. If
you are interested in finding out more, there are many other materials you can
reference. One great (and free reference) is Allen Downey's book on
concurrency [6]. This book has lots of puzzles you can work on to improve your
understanding of both semaphores in specific and concurrency in general.
Becoming a real concurrency expert takes years of effort; going beyond what
you learn in class is a key component to mastering such a topic.


[REFERENCES]

[1] "A Note on Two Problems in Connexion with Graphs", E. W. Dijkstra. 
Numerische Mathematik 1, 269–271, 1959. 
Available: http://www-m3.ma.tum.de/twiki/pub/MN0506/WebHome/dijkstra.pdf
Can you believe people worked on algorithms in 1959? I can't.

[2] "Go-to Statement Considered Harmful", E.W. Dijkstra. 
Communications of the ACM, volume 11(3): pages 147–148, March 1968.
Available: http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD215.PDF

[3] "The Structure of the THE Multiprogramming System", E.W. Dijkstra.
Communications of the ACM, volume 11(5), pages 341–346, 1968. 
It's pronounced "the T - H - E operating system", not "the THE operating
system". Interestingly, this paper that introduced semaphores actually was an
early paper on the art and science of operating system design. Semaphores,
which Dijkstra developed to aid in the process of writing the heavily
concurrent OS, are only found in the appendix of the paper, almost as an
afterthought! Indeed, both the use of semaphores as locks as well as
semaphores as condition variables are found in the appendix of this paper.

[4] Historically, sem_wait() was first called P() by Dijkstra (for the dutch
word "to probe") and sem_post() was called V() (for the dutch word "to test"). 

[5] "Information Streams Sharing a Finite Buffer", E.W. Dijkstra.
Information Processing Letters 1: 179–180, 1972.
Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD329.PDF
Did Dijkstra invent everything? No, but close. He even worked on 
one of the first systems to correctly provide interrupts!

[6] "The Little Book of Semaphores", A.B. Downey.
Available: http://greenteapress.com/semaphores/
A great and free book about semaphores.

[7] "Real-world Concurrency", Bryan Cantrill and Jeff Bonwick.
ACM Queue. Volume 6, No. 5. September 2008.
Available: http://www.acmqueue.org/modules.php?name=Content&pa=showpage&pid=554

[8] "My recollections of operating system design", E.W. Dijkstra.
April, 2001. Circulated privately. 
Available: http://www.cs.utexas.edu/users/EWD/ewd13xx/EWD1303.PDF
A fascinating read for those of you interested in how the pioneers
of our field came up with some very basic and fundamental concepts,
including ideas like "interrupts" and "a stack"!

[9] "Hierarchical ordering of sequential processes", E.W. Dijkstra.
Available: http://www.cs.utexas.edu/users/EWD/ewd03xx/EWD310.PDF
The wikipedia page about this problem is also quite informative.
Kaynak: Remzi Arpaci