天天看點

嵌入式Linux _ 線程專題

一、線程的基本概念 及  建立

1、線程的概念(了解)

—  程序的關系

  • 程序是 運作一段程式 系統給其配置設定資源的總稱;
  • 程序有獨立的位址空間;每個程序互不影響,隻能通路自己空間的資料;
  • Linux為每個程序建立task_struct 結構體;
  • 每個程序都參與核心排程,互不影響;

— 、為啥需要線程,線程的作用

  • 程序再切換時對系統的開銷很大;
嵌入式Linux _ 線程專題

  以上如圖:

  1. 程序(P1、P2)建立後 系統将資料放入記憶體(RAM)中,CPU去執行某一個程序時,需要去從程序中擷取指令,要去取址、譯碼、執行,還要去通路資料,這些東西都是放在記憶體中的,cpu需要去記憶體中擷取指令或資料區執行,處理。cpu頻率很快,相對向記憶體讀取要快的多,中間加一個Cache(高速緩存:靜态RAM),每次運作程序前,将程序的部分資料放入cache中,cpu直接在cache中讀取資料。
  2. 當程序間切換時,cache需要重新整理,還有一種 tlb(頁表緩存),每個程序的實體位址是不一樣的。
  • 很多作業系統引入輕量級程序LWP(線程)。
  • 同一程序中的線程共享相同位址空間。
  • Linux中不區分 程序、線程。

2、線程特點(了解)

  • 通常線程指的是共享相同位址空間的多個任務。
  • 使用多線程的好處:
    1. 大大提高了任務切換的效率。
    2. 避免了額外的TLB&cache的重新整理。
嵌入式Linux _ 線程專題

一個程序中。

  • 一個程序中的多個線程共享以下資源。

         線程ID(TID)

         PC(程式計數器)和相關寄存器。

        堆棧

        錯誤号(errno);

        優先級;

       執行狀态和屬性;

  • pthread線程庫中提供了如下基本操作。

       建立線程;

       回收線程;

       結束線程;

  • 同步和互斥機制

       信号量;

        互斥鎖;

4、線程的建立(熟悉)

— 線程建立 — pthread_create

        #include <pthread.h>

       int pthread_create(pthread_t *thread , const pthread_attr_t *attr , void*(routine)(void*),  void *arg);

  • 成功傳回0,失敗傳回錯誤碼;
  • thread 線程對象;
  • attr :線程屬性,NULL代表預設屬性;
  • routine : 線程執行的函數;
  • arg : 傳遞給routine 的參數;

— 線程回收 — pthread_join

      #include <pthread.h>

     int pathread_join(pthread_t thread , void ** retval);

  • 成功傳回0,失敗時傳回錯誤碼;
  • thread 要回收的線程對象;
  • 調用線程阻塞直到thread結束;
  • *retval 接收線程thread的傳回值;(一個二級指針傳回的是一個位址)

— 結束一個線程 — pthread_exit

    #include <pthread.h>

    void pthread_exit(void*retval);

  • 結束目前程序;
  • retval可被其他線程通過pthread_join擷取。(傳回的retval位址不能是一個局部變量的位址,不然該空間被釋放,會出錯)

示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

char message[32] = "Hello World";
void *thread_func(void *arg);


int main(int argc, const char *argv[])
{
	pthread_t a_thread;
	void *result;

	if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0)
	{
		printf("fail to pthread_create");
		exit(-1);
	}
	pthread_join(a_thread,&result);   //線程回收
	printf("result is %s \n",(char*)result);
	printf("message is %s \n",message);

	return 0;
}

void *thread_func(void *arg)
{
	sleep(1);   //
	strcpy(message,"markey by thread");
	pthread_exit("thank you for waiting for me"); //線程退出
}
           
  • 線程時通過第三方的庫函數實作的,在編譯時要加入相應的庫。
  • 之前講過  多程序中 父程序結束時,子程序還會再運作;但在多線程中:當程序中的主線程man傳回結束時,還程序中的所有線程都會結束。

二、線程兼通信的問題

1、同步機制(了解)

  • 線程共享同一程序的位址空間;
  • 優點:線程間通信很容易
    1. 通過全局變量交換資料;
  • 缺點:多個線程通路共享資料時需要同步或互斥機制。
嵌入式Linux _ 線程專題

2、線程通信 — 同步

  • 同步(synchronization)指的是多個任務按照約定的先後次序互相配合完成一件事。(後完成的任務通常都需要等待 先完成的任務完成,先完成的任務完成後通知後任務)
  • 1968年,Edsgar Dijktra基于信号量的概念 提出了一種 同步進制。
  • 由信号量來決定線程是繼續運作還是 阻塞等待;

3、信号量(了解)

  • 信号量(燈):信号量代表某一類資源,其值表示系統中該資源的數量(沒資源為0,有資源大于0)
  • 信号量是一個受保護的變量,智能通過三種操作來通路。
    1. 初始化;
    2. P操作(申請資源):檢查信号量的值是否大于0,大于0有資源,任務繼續執行,去通路;等于0時沒資源,線程阻塞,等待該資源。
    3. V操作(釋放資源): 任務不需要通路資源,或者任務産生了一個資源,告訴其他需要該資源的任務。

4、對信号量基本操之 — P操作(熟悉)

  • 信号量  ---  P

             P(S)含義如下:

          if(信号量的值大于0)

         {

             申請資源的任務繼續運作;

              信号量的值減一;

        }else

        {

           申請資源的任務阻塞;

         }

5、對信号量的 V操作(熟悉)

  • 信号量  ------  V
  • V(S)含義如下:

              信号量的值加一;

       if(有任務在等待資源)

      {

           喚醒等待的任務,讓其繼續運作;

       }

6、對應相應函數

— Posix信号量

  • posix中定義了兩種信号量;
    1. 無名信号量(基于記憶體的信号量):主要用于程序内部,線程之間的通信(同步);
    2. 有名信号量(既可以用于程序之間,也可以用于線程之間);
  • pthread庫常用的信号量操作函數如下:

            int  sem_init(sem_t *sem , int pshared , unsigned  int value);

            int sem_wait(sem_t *sem); P操作

            int sem_post(sem_t *sem); V操作

2、 信号量初始化  --   sem_init

  • #include  <semaphore.h>
    1. int  sem_init(sem_t *sem , int pshared , unsigned  int value);
    2. 成功時傳回0,失敗時傳回 EOF;
    3. sem 指向要初始化的信号量對象;
    4. pashared  0 — 線程間使用, 1— 程序間使用;
    5. val  : 信号量初值;

3、信号量操作 — P/V操作

  • #include  <semaphore.h>
  • int sem_wait(sem_t *sem); P操作
  • int sem_post(sem_t *sem); V操作
  1. 成功時傳回0,失敗時傳回EOF;
  2. sem指向要操作的信号量對象;

三、信号量同步示例1、2

1、信号量同步示例1 (熟練)

  • 兩個線程同步讀寫緩沖區(生産者/消費者問題)
嵌入式Linux _ 線程專題

— 寫緩沖區線程 : writer    讀緩沖區線程 : reader

— 寫緩沖區對信号量進行一個P操作,并喚醒 讀程序、都程序喚醒後對信号量進行一個V操作。

#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


char buf[32];
sem_t sem;
void *function(void *arg);
int main(int argc, const char *argv[])
{
	pthread_t a_thread;

	if(sem_init(&sem,0,0) < 0)
	{
		perror("sem_init");
		exit(-1);
	}
	if(pthread_create(&a_thread,NULL,function,NULL) != 0)
	{
		perror("pthread_create");
		exit(-1);
	}
	do
	{
		fgets(buf,32,stdin);   //按行标準輸入
		sem_post(&sem); //對信号量進行V操作,擷取/釋放資源
	}while(strncmp(buf,"quit",4) != 0);

	return 0;
}

void *function(void *arg)
{
	while(1)
	{
		sem_wait(&sem);//對信号量進行P操作,申請資源
		printf("you enter %d characters \n",strlen(buf));
	}
}
           

— ps  aux –L |grep sem

2、信号量同步示例2 (熟練)

嵌入式Linux _ 線程專題
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


char buf[32];
sem_t sem_r;
sem_t sem_w;
void *function(void *arg);
int main(int argc, const char *argv[])
{
	pthread_t a_thread;

	if(sem_init(&sem_r,0,0) < 0)
	{
		perror("sem_init");
		exit(-1);
	}
	if(sem_init(&sem_w,0,1) < 0)
	{
		perror("sem_init");
		exit(-1);
	}
	if(pthread_create(&a_thread,NULL,function,NULL) != 0)
	{
		perror("pthread_create");
		exit(-1);
	}
	do
	{
		sem_wait(&sem_w);
		fgets(buf,32,stdin);
		sem_post(&sem_r); //對信号量進行V操作,擷取/釋放資源
	}while(strncmp(buf,"quit",4) != 0);

	return 0;
}

void *function(void *arg)
{
	while(1)
	{
		sem_wait(&sem_r);//對信号量進行P操作,申請資源
		printf("you enter %d characters \n",strlen(buf));
		sem_post(&sem_w);
	}
}
           

注:任何信号量都要進行初始化。

小結:  sem_init

            sem_wait

            sem_post

四、程序間的互斥

1、臨界資源(了解)

  • 臨界資源
    1. 通常是一個共享資源;
    2. 一次隻能允許被一個任務(程序、線程)通路;
  • 臨界區
    1. 通路臨界區的代碼;(其他代碼成為非臨界代碼)
  • 互斥機制
    1. mutex互斥鎖;
    2. 要不沒有任務持有該鎖,要不隻有一個任務隻有該鎖。
    3. 任務通路臨界資源前申請鎖,通路完後釋放鎖;

2、互斥機制

  • 互斥鎖(熟悉)
  1. 互斥鎖初始化  —  pthread_mutex_init

         #include  <pthread.h>

          int pathread_mutex_init(pathread_mutex_t *mutex , const pthread_mutexattr_t * attr);

  • 成功時傳回0,失敗時傳回錯誤碼;
  • mutex 指向要初始化 的互斥 鎖對象;
  • attr  : 互斥鎖的屬性,NULL表示預設屬性;
  1. 申請鎖 — pthread_mutex_lock

         #include  <pthread.h>

          int pthread_mutex_lock(pthread_mutex_t * mutex);

  • 成功時傳回0,失敗時傳回錯誤碼;
  • mutex 指向要 初始化的互斥鎖對象;
  • 如果無法獲得鎖,任務阻塞;
  1. 釋放鎖  —  pthread_mutex_unlock

          #include  <pthread.h>

         int  pthread_mutex_unlock(pthread_mutex_t * mutex);

  • 成功時傳回0,失敗時傳回錯誤碼;
  • mutex 指向要 初始化的互斥鎖對象;
  • 執行完臨界區要及時釋放鎖;

示例:

嵌入式Linux _ 線程專題
嵌入式Linux _ 線程專題
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

unsigned int count,value1,value2;
pthread_mutex_t lock;
void *thread_func(void *arg);


int main(int argc, const char *argv[])
{
	pthread_t a_thread;


	if(pthread_mutex_init(&lock,NULL) != 0)
	{
		printf("fail to pthread_mutex_init  \n");
		exit(-1);
	}
	if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0)
	{
		printf("fail to pthread_create");
		exit(-1);
	}
	while(1)
	{
		count++;		
#ifdef _LOCK_
		pthread_mutex_lock(&lock);
#endif
		value1 = count;
		value2 = count;
#ifdef _LOCK_
		pthread_mutex_unlock(&lock);
#endif
		
	}
	return 0;
}

void *thread_func(void *arg)
{
	while(1)
	{
#ifdef _LOCK_
		pthread_mutex_lock(&lock);
#endif
		if(value1 != value2)
		{
			printf("value1 =%u , value2 = %u \n",value1,value2);
			usleep(100000);//将線程挂起一段時間,機關微妙
		}
#ifdef _LOCK_
		pthread_mutex_unlock(&lock);
#endif
		//printf("value1 =%u , value2 = %u \n",value1,value2);
	}
	return NULL;
}
           

 注:

      不使用互斥鎖運作:$gcc -o test test.c -Wall -lpthread

                                     $./test

                                      value1 = 198 , value2 = 199;

                                      value1 = 2398 , value2 = 2399;

      使用互斥鎖運作:$gcc -o test test.c -Wall -lpthread _D_LOCK_

                                     $./test

總結: (1) 程序與 線程量程在時間片的作用下 互動執行值,count的值一直在疊加的狀态;

          (2)在沒有使用互斥鎖的時候,程序中再給value1或者value2指派的時候可能會遇到時間片使用完的情況,會有當次某    一個指派失敗的情況,導緻跳入線程中後判定兩值不相等,列印出對應數值;

         (3)加了互斥鎖以後,在申請鎖成功以後,在跳入另一個程序中時,另一個程序也在申請鎖時,會失敗進入阻塞狀态,  又會傳回擁有鎖的程序繼續執行,直到程序釋放鎖,另一個程序得到執行,這是不是出現(2)中的情況;