作業系統課程實踐報告

qq_42611126發表於2018-07-04

課程總結

1 執行緒的建立與啟動

1.1 程式與執行緒

1、程式(process

狹義定義:程式就是一段程式的執行過程。

廣義定義:程式是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元,在傳統的作業系統中,程式既是基本的分配單元,也是基本的執行單元。

簡單的來講程式的概念主要有兩點第一,程式是一個實體。每一個程式都有它自己的地址空間,一般情況下,包括文字區域(text region)、資料區域(data region)和堆疊(stack region)。文字區域儲存處理器執行的程式碼;資料區域儲存變數和程式執行期間使用的動態分配的記憶體;堆疊區域儲存著活動過程呼叫的指令和本地變數。第二,程式是一個執行中的程式。程式是一個沒有生命的實體,只有處理器賦予程式生命時,它才能成為一個活動的實體,我們稱其為程式。

程式狀態:程式有三個狀態,就緒、執行和阻塞。就緒狀態其實就是獲取了出cpu外的所有資源,只要處理器分配資源就可以馬上執行。就緒狀態有排隊序列什麼的,排隊原則不再贅述。執行態就是獲得了處理器分配的資源,程式開始執行。阻塞態,當程式條件不夠時候,需要等待條件滿足時候才能執行,如等待i/o操作時候,此刻的狀態就叫阻塞態。

2、程式

說起程式,就不得不說下程式。先看定義:程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。而程式則是在處理機上的一次執行過程,它是一個動態的概念。這個不難理解,其實程式是包含程式的,程式的執行離不開程式,程式中的文字區域就是程式碼區,也就是程式。

3、執行緒

通常在一個程式中可以包含若干個執行緒,當然一個程式中至少有一個執行緒,不然沒有存在的意義。執行緒可以利用程式所擁有的資源,在引入執行緒的作業系統中,通常都是把程式作為分配資源的基本單位,而把執行緒作為獨立執行和獨立排程的基本單位,由於執行緒比程式更小,基本上不擁有系統資源,故對它的排程所付出的開銷就會小得多,能更高效的提高系統多個程式間併發執行的程度。

4、多執行緒

在一個程式中,這些獨立執行的程式片段叫作執行緒Thread),利用它程式設計的概念就叫作多執行緒處理。多執行緒是為了同步完成多項任務,不是為了提高執行效率,而是為了提高資源使用效率來提高系統的效率。執行緒是在同一時間需要完成多項任務的時候實現的。

最簡單的比喻多執行緒就像火車的每一節車廂,而程式則是火車。車廂離開火車是無法跑動的,同理火車也不可能只有一節車廂。多執行緒的出現就是為了提高效率。

 

程式與執行緒的區別:

程式和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響,而執行緒只是一個程式中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程式。

1) 簡而言之,一個程式至少有一個程式,一個程式至少有一個執行緒.

2) 執行緒的劃分尺度小於程式,使得多執行緒程式的併發性高。

3) 另外,程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。

4) 執行緒在執行過程中與程式還是有區別的。每個獨立的執行緒有一個程式執行的入口、順序執行序列和程式的出口。但是執行緒不能夠獨立執行,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。

5) 從邏輯角度來看,多執行緒的意義在於一個應用程式中,有多個執行部分可以同時執行。但作業系統並沒有將多個執行緒看做多個獨立的應用,來實現程式的排程和管理以及資源分配。這就是程式和執行緒的重要區別。

 

優缺點 
執行緒和程式在使用上各有優缺點:執行緒執行開銷小,但不利於資源的管理和保護;而程式正相反。同時,執行緒適合於在SMP機器上執行,而程式則可以跨機器遷移。

1.2 Java中的ThreadRunnable

1.3 三種建立執行緒的辦法

第一種:

package bao;

/**

 * java.doc

 * @author Administrator

 *

 */

class MyR implementsRunnable{

   private String msg;

 

   public MyR(String msg) {

     this.msg = msg;

    

   }

 

   @Override

   publicvoid run() {

     while(true) {

        try {

          Thread.sleep(1000);

          System.out.println(msg);

        }catch(InterruptedException e) {

          // TODO Auto-generated catch block

          e.printStackTrace();

          break;

        }

   }

   }

  

}

publicclassTestthread {

 

   publicstaticvoid main(String[] args) {

     Threadthread1= newThread(newMyR("hello"));

     thread1.start();

     Threadthread2= newThread(newMyR("hhhhh"));

     thread2.start();

    

   }

 

}

第二種:

package bao;

 

publicclassTesttheard2 {

 

   publicstaticvoid main(String[] args) {

     Testtheard2testtheard2= newTesttheard2();

        //匿名信匿名類

     Runnablerunnable= new Runnable() {

            @Override

          publicvoid run() {

            while(true) {

             try {

               Thread.sleep(1000);

               System.out.println("haha");

             }catch(InterruptedException e) {

               // TODO Auto-generated catch block

               e.printStackTrace();

               break;

             }

            

            }

            }

        };

       

        Threadthread= newThread(runnable);

        Thread.start();

   }

 

}

 

第三種:

package bao;

 

import javax.xml.stream.events.StartDocument;

 

publicclassTestthread3 {

 

   publicstaticvoid main(String[] args) {

      new Thread(newRunnable() {

       

        @Override

        publicvoid run() {

          while(true) {

             try {

               Thread.sleep(1000);

               System.out.println("123");

             }catch(InterruptedException e) {

               // TODO Auto-generated catch block

               e.printStackTrace();

               break;

             }

         

        }

     }

 

   }).start();

      new Thread(()->{

      System.out.println("123");

         

      }).start();

   }

 

}

 

2 執行緒簡單同步(同步塊)

2.1 同步的概念和必要性

同步就是協同步調,按預定的先後次序進行執行。如:你說完,我再說。

字從字面上容易理解為一起動作

其實不是,字應是指協同、協助、互相配合。

如程式、執行緒同步,可理解為程式或執行緒AB一塊配合,A執行到一定程度時要依靠B的某個結果,於是停下來,示意B執行;B依言執行,再將結果給AA再繼續操作。

所謂同步,就是在發出一個功能呼叫時,在沒有得到結果之前,該呼叫就不返回,同時其它執行緒也不能呼叫這個方法。按照這個定義,其實絕大多數函式都是同步呼叫(例如sin, isdigit等)。但是一般而言,我們在說同步、非同步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。例如Window API函式SendMessage。該函式傳送一個訊息給某個視窗,在對方處理完訊息之前,這個函式不返回。當對方處理完畢以後,該函式才把訊息處理函式所返回的LRESULT值返回給呼叫者。

在多執行緒程式設計裡面,一些敏感資料不允許被多個執行緒同時訪問,此時就使用同步訪問技術,保證資料在任何時刻,最多有一個執行緒訪問,以保證資料的完整性。

為什麼要同步?

因為當我們有多個執行緒要同時訪問一個變數或物件時,如果這些執行緒中既有讀又有寫操作時,就會導致變數值或物件的狀態出現混亂,從而導致程式異常。舉個例子,如果一個銀行賬戶同時被兩個執行緒操作,一個取100塊,一個存錢100塊。假設賬戶原本有0塊,如果取錢執行緒和存錢執行緒同時發生,會出現什麼結果呢?取錢不成功,賬戶餘額是100.取錢成功了,賬戶餘額是0.那到底是哪個呢?很難說清楚。因此多執行緒同步就是要解決這個問題。

2.2 synchronize關鍵字和同步塊

Synchronize 關鍵字是解決併發問題常用解決方案,有以下三種使用方式:

·        同步普通方法,鎖的是當前物件。

·        同步靜態方法,鎖的是當前 Class 物件。

·        同步塊,鎖的是 {} 中的物件。

 

同步塊:

1.從包含的程式碼量來說,同步程式碼塊比同步方法小。我們可以把同步程式碼塊比喻成是沒上鎖房間裡的多了一層帶鎖的內門。

2.帶鎖的內門可以由使用者自定義其鑰匙,你可以用本房的鑰匙,你也可以指定用一個房子的鑰匙才能開,這樣的話,你要跑到另一棟房子那兒把那個鑰匙拿來,並用那個房子的鑰匙來開啟這個房子的帶鎖的內門。

3.記住你獲得的那另一棟房子的鑰匙,並不影響其他人進入那棟房子沒有鎖的房間。

 

2.3 例項

package bao;

import java.util.ArrayList;

import javax.swing.text.StyledEditorKit.ForegroundAction;

 

import com.sun.media.jfxmedia.events.NewFrameEvent;

 

publicclasstest5 {

 staticintc=0;

privatestaticObject lock=newObject();//建立一個鎖變數

   publicstaticvoid main(String[] args) {

     Thread[]threads= newThread[1000];

   for(inti=0;i<1000;i++) {

     finalintindex =i;

     threads[i]= new Thread(()->{

        synchronized(lock) {//建立一個同步塊,需要一個鎖

          System.out.println("thread "+index+" enter");//輸出

     inta = c;

     a++;

      try {

        Thread.sleep((long) (Math.random()*1000));

     }catch(Exception e) {

        e.printStackTrace();

     }

     c=a;//存回去

     System.out.println("thread "+index+" leave");

        }

   });

   threads[i].start();

 }

   for(inti=0;i<1000;i++) {

     try {

        threads[i].join();

     }catch(InterruptedException e) {

        e.printStackTrace();

     }

   }

   System.out.print("c="+c);

 

}

}

 

3 生產者消費者問題

3.1 問題表述

生產者消費者問題,也稱有限緩衝問題,是一個多執行緒同步問題的經典案例。該問題描述了兩個共享固定大小緩衝區執行緒——即所謂的生產者消費者”——在實際執行時會發生的問題。生產者的主要作用是生成一定量的資料放到緩衝區中,然後重複此過程。與此同時,消費者也在緩衝區消耗這些資料。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入資料,消費者也不會在緩衝區中空時消耗資料。

3.2 實現思路

要解決該問題,就必須讓生產者在緩衝區滿時休眠,等到下次消費者消耗緩衝區中的資料的時候,生產者才能被喚醒,開始往緩衝區新增資料。同樣,也可以讓消費者在緩衝區空時進入休眠,等到生產者往緩衝區新增資料之後,再喚醒消費者。通常採用程式間通訊的方法解決該問題,常用的方法有訊號燈法等。如果解決方法不夠完善,則容易出現死鎖的情況。出現死鎖時,兩個執行緒都會陷入休眠,等待對方喚醒自己。

3.3 Java實現該問題的程式碼

生產者消費者
佇列Queue

package org.yang;

 

import java.util.LinkedList;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

publicclassQueue { //佇列

   private Lock lock = new ReentrantLock();

   private Condition fullC; //訊號量

   private Condition emptyC; //訊號量 

   privateintsize;

   public Queue(intsize) {

     this.size  = size;

     fullC = lock.newCondition();

     emptyC = lock.newCondition();

   }

   LinkedList<Integer>list= newLinkedList<Integer>();

   /**

    * 入隊

    * @return

    */

   publicboolean EnQueue(intdata) {

     lock.lock();

     while(list.size()>=size) {

        try {

          fullC.await();

        }catch(InterruptedException e) {

          lock.unlock();

          returnfalse;

        }

     }

     list.addLast(data);

     emptyC.signalAll();

     lock.unlock();

     returntrue;

   }

   /**

    * 出隊

    * @return

    */

   publicint DeQueue() {

     lock.lock();

     if(list.size() == 0) {

        try {

          emptyC.await();

        }catch(InterruptedException e) {

          lock.unlock();

           return -1;

        }

     }

     intr = list.removeFirst();

     fullC.signalAll();

     lock.unlock();

     returnr;

   }

   publicboolean isFull() {

     returnlist.size()>=size;

   }

   publicboolean isEmpty() {

     returnlist.size()==0;

   }

}

生產者消費者

package org.yang;

 

import java.util.concurrent.ThreadLocalRandom;

 

publicclassTestA {

   staticQueue queue= newQueue(5);

   publicstaticvoid main(String[] args) {

     for(inti=0;i<3;i++) {

        finalintindex = i;

        new Thread(()->{

          intdata =(int)(Math.random()*1000);

           System.out.printf("produce thread %d want to EnQueue %d\n",index,data);

          queue.EnQueue(data);

          System.out.printf("produce thread %d EnQueue %d Success\n",index,data);

          sleep();

        }).start();

     }

     for(inti=0;i<3;i++) {

        finalintindex=i;

        new Thread(()->{

          System.out.printf("customer thread %d want to EnQueue\n",index);

          intdata = queue.DeQueue();

          System.out.printf("customer thread %d EnQueue %d Success\n",index,data);

        }).start();

     }

   }

   publicstaticvoid sleep() {

     intt =(int)(Math.random()*100);

     try {

        Thread.sleep(t);

     }catch(Exception e) {

        // TODO: handle exception

        e.printStackTrace();

     }

   }

   }

 

3.4 測試

3.4.1 當生產能力超出消費能力時的表現

produce thread 0 want to EnQueue 931

produce thread 0 EnQueue 931 Success

customer thread 1 want to EnQueue

customer thread 1 EnQueue 931Success

produce thread 2 want to EnQueue 185

produce thread 2 EnQueue 185 Success

customer thread 2 want to EnQueue

customer thread 2 EnQueue 185Success

customer thread 0 want to EnQueue

produce thread 1 want to EnQueue 878

produce thread 1 EnQueue 878 Success

customer thread 0 EnQueue 878Success

 

3.4.2 當生產能力弱於消費能力時的表現

produce thread 0 want to EnQueue 4

produce thread 0 EnQueue 4 Success

customer thread 1 want to EnQueue

customer thread 1 EnQueue 4 Success

produce thread 2 want to EnQueue 7

produce thread 2 EnQueue 7 Success

customer thread 2 want to EnQueue

customer thread 2 EnQueue 7 Success

customer thread 0 want to EnQueue

produce thread 1 want to EnQueue 9

produce thread 1 EnQueue 9 Success

customer thread 0 EnQueue 9 Success

4.總結

從理論到實踐,確實學到了很多東西,在除錯過程中遇到了很多的麻煩,但通過老師的講解和同學們的幫助成功解決了這些問題,使我明白了編寫程式一點都不能馬虎,通過這次實踐,我更加深刻的瞭解到了執行緒和程式的區別,經過了程式設計,懂得了建立執行緒的方法,瞭解了簡單的執行緒同步,更加深入的瞭解了生產者消費者的問題。


相關文章