35.Linux應用層開發---線程

 

一.線程的概念和使用

1.線程的概念

爲了進一步減少處理器的空轉時間,支持多處理器以及減少上下文切換開銷,進程在演化中出現了另外一個概念線程。

它是進程內獨立的一條運行路線,是內核調度的最小單元,也被稱爲輕量級的進程。

以前對線程和進程真的是傻傻分不清,面試中也經常會出現線程和進程的區別之類的問題。其實也比較好區分。

從上圖我們可以看出線程和進程的關係,圖片上看感覺線程是進程的子集?其實線程屬於輕量級的進程,假如我們的qq是一個進程,那麼裏面的聊天功能,打字功能等子模塊也需要一個個進程來完成嗎??顯然是可以完成的,但是感覺有點大材小用,切換時系統開銷肯定是很大的,用戶體驗也會非常的差,那麼就出現了線程。

同一進程中的線程共享相同的地址空間。當然線程有共有的部分,也有自己私有的部分(TCB 堆棧 寄存器等)。

2.線程的特點:

1> 大大提高了任務的切換效率

2> 避免了額外的TLB & cache的刷新。那什麼是cache,它其實就是一個高速內存,它很快到但是它也很小,cpu存取要先從cache中找,當沒有的時候cpu會通過TLB(內存映射),將內存中的數據放入cache中,如果進程較大,就需要cache頻繁切換,效率下降。

3.進程資源佔用

一個進程中可以有多個線程,線程之間有一些資源是共享的 ,就像進程一樣,有私有的也有自己獨有的部分。

一般共享以下資源:

可執行的指令,靜態數據,進程中打開的文件描述符,當前工作目錄,用戶ID,用戶組ID

私有資源部分:

線程ID (TID),PC(程序計數器)和相關寄存器,堆棧,錯誤號 (errno),優先級,執行狀態和屬性

 

二.線程的基本操作

需要使用線程庫,pthread線程庫中提供瞭如下基本操作

1.創建線程

 #include  <pthread.h>

 int  pthread_create(pthread_t *thread, const

       pthread_attr_t *attr, void *(*routine)(void *), void *arg);

例:

void *funct(void* arg){
	printf("this is thread ");
	sleep(5);
}

int main(){
	int re,i;
	pthread_t tid;
	for(i=0;i<100;i++){
		re = pthread_create(&tid, NULL,funct, NULL);
		pthread_detch(tid); //後面會講,用於回收
		if(re!=0){
			printf("pthread_create:%s\n",strerror(re));
			exit(0);
		}
	}
	
    sleep(1); //需要加延時,不然進程就結束了,線程得不到運行也跟着退出了
}

注:進程結束,線程也會跟着結束。

ps -eLf|grep cthread //來查看進程中的線程

多線程程序中,任何一個線程使用exit(0)都會導致整個進程結束。

2.回收線程

線程不回收就會出現殭屍線程,同樣也要回收。

先查看線程ID

然後top -p 2933(進程號) 查看內存佔用。

 #include  <pthread.h>

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

 if (pthread_create(&tid, NULL, thread_func, NULL) != 0){
        printf(“fail to pthread_create”);  exit(-1);
 }
	
 re = pthread_join(tid,NULL);
		if(re!=0){
		    printf("pthread_join:%s\n",strerror(re));
		    return;
            }
 }	

這樣就完成指定線程的釋放,此時的第二個參數下面會用到。

3.結束線程

void  pthread_exit(void *retval); 返回值,可被pthread_join回收,通常配合join使用

 void *funct(void* arg){
	int ret;
	printf("this is funct thread\n");
	sleep(1);
	ret = 5;
	pthread_exit((void*)ret); //線程退出,將參數返回
}
int main(){
	
	int re,i;
	pthread_t tid;

	re = pthread_create(&tid, NULL,funct, (void *)i);	
	
	
	void *retval;
	pthread_join(tid,&retval);// 獲取exit的返回參數
	printf("retval=%d\n",(int)retval);
	
}

4.線程取消

int pthread_cancel(pthread_t thread);

本質上給線程發一個信號,這個使用前要確保線程是能被取消屬性,並且是不是在cancel點(阻塞函數 sleep等)上此時才能取消成功.

還有一些配合使用的函數:

1》 int pthread_setcancelstate(int state, int *oldstate); 設置線程屬性是可以被取消還是不可以,如果設置了不可取消屬性,調用pthread_cancel,也不能被取消。

例:pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//設置爲不可取消

2》int pthread_setcanceltype(int type, int *oldtype);設置時延時取消還是立即取消

如果爲延時取消,就運行到cancel點時取消,阻塞函數,sleep等都是cancel點.

如果沒有cancel點呢??可以人爲的加一個void pthread_testcancel(void);通過這個函數。立即取消不需要cancel點。

5.修改detach屬性

pthread_detach,改變爲detach屬性後,不需要再用join進行回收,感覺這個更方便。

re = pthread_create(&tid, NULL,funct, (void *)i);

pthread_detach(tid);

 

三.線程間同步互斥機制

我們在同一個進程中創建了多個線程,他們共享地址空間,通過全局變量就可以進行數據的交互,當時多個線程訪問共享資源的時候就需要同步和互斥,和實時系統中是一樣的。以前我們在實時系統中都知道用信號量進行操作,互斥信號量進行互斥,這裏都是一樣的。

在linux中它有更洋氣的名字叫原子操作。

操作方式:

1.初始化

2.P操作(申請資源)

3.V操作(釋放資源)

原理上沒啥區別。

posix中定義了兩類信號量:

無名信號量(基於內存的信號量)只用於線程間同步和互斥

有名信號量,保存在文件中,可以用於線程也可以用於進程的同步和互斥

1.線程的同步

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操作

例:

#define NUM 64
sem_t sem;
char buf[NUM];
void * writeBuff(void * arg){
	while(1){
		fgets(buf,NUM,stdin);
		sem_post(&sem);  //P操作
	}
}
void *readBuff(void * arg){
	while(1){
		sem_wait(&sem); //V操作
		printf("buf=%s\n",buf);
		memset(buf,0,NUM);
	}
}
int main(){
	
	pthread_t tid1,tid2;
	int re;
	sem_init(&sem,0,0);//初始化信號量
	re = pthread_create(&tid1, NULL,writeBuff, NULL);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}
	re = pthread_create(&tid2, NULL,readBuff, NULL);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}	
	while(1){
		sleep(1);
	}
	
}

2.線程的互斥

mutex互斥鎖,和我們平時用的也沒啥區別,也是一種信號量

任務訪問臨界資源前申請鎖,訪問完後釋放鎖

int  pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *  attr);

int  pthread_mutex_lock(pthread_mutex_t *mutex);申請鎖

int  pthread_mutex_unlock(pthread_mutex_t *mutex);釋放鎖

FILE *fp;

pthread_mutex_t mutex;

void *write1(void* arg){
	int a=0;
    a = (int)arg;
	int len,i;
	char *c1 = "Hello world\n";
	char *c2;
	len = strlen(c1);
	int td = pthread_self();
	pthread_detach(pthread_self());
	c2 = c1;
	while(1){
	   
	   pthread_mutex_lock(&mutex);//上鎖
	   for(i=0;i<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解鎖
	   c1 = c2;
	   sleep(1);
	}
}

void *write2(void* arg){
	int a=0;
    a = (int)arg;
	int len,i;
	char *c1 = "How are your\n";
	char *c2;
	c2 = c1;
	len = strlen(c1);
	int td = pthread_self();
	pthread_detach(pthread_self());
	while(1){
	   
	   pthread_mutex_lock(&mutex);//上鎖	
	   for(i=0;i<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解鎖
	   c1 = c2;
	   sleep(1);	
	}

}

int main(){
	int re,i=0;
	pthread_t tid1,tid2;
	
	fp = fopen("1.txt","w");
	if(!fp){
		perror("fopen");
		return -1;
	}
	pthread_mutex_init(&mutex,NULL);
	
	re = pthread_create(&tid1, NULL,write1, (void *)i);
	pthread_detach(tid1);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}
	re = pthread_create(&tid2, NULL,write2, (void *)i);
	pthread_detach(tid2);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}	
	while(1){
		sleep(1);
	}

}

注:我們在使用線程函數的時候要注意,我們要使用線程庫,在編譯的時候也要注意要鏈接上庫,不然編譯會報錯

gcc -o pthread pthread.c -lpthread  詳細的可以查看庫的生成那一節

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章