天天看點

Linux多線程程式設計---2

1. 線程概念:線程是包含在程序内部的順序執行流,是程序中實際運作機關,也是作業系統能夠進行排程的最小機關,一個程序中可以并發多條線程,每條線程并行執行不同的任務。

2. 線程與程序的關系

    1> 一個線程隻能屬于一個程序,但是一個程序可以有多個線程,但至少有一個主線程。

    2> 同一程序的所有線程共享該程序的所有資源。

    3> 線程作為排程和配置設定的基本機關,程序作為擁有資源的基本機關。

    4> 程序是擁有資源的一個獨立機關,線程不擁有系統資源,但是可以通路隸屬于程序的資源

3.  多線程的優勢

    1> 友善的通信和資料交換,同一程序下的線程之間共享資料空間,是以一個線程的資料可以直接為其他線程所用

    2> 更高效的利用CPU,提高應用程式的響應。

demo1: 主線程建立5個線程,這5個線程和主線程并發執行,主線程建立完線程後調用pthread_exit()函數退出線程,在每個線程内分别列印目前線程的序号。

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

#define Threads_number 5

void *PrintHello(void* threadid)
{
	long tid;
	tid = (long)threadid;//強制類型轉換
	
	printf("Hello World!It's me, thread #%ld!\n", tid);
	
	pthread_exit(NULL);//退出線程
}

int main(int argc, char* argv[])
{
	pthread_t threads[Threads_number];
	int rc;
	long t;
	
	for(t = 0; t < Threads_number; t++)
	{
	    printf("In main: creating thread %ld\n", t);
		//建立線程并傳入參數
		rc = pthread_create(&threads[t], NULL, PrintHello,(void*) t);
		
		if(rc)
		{
			printf("ERROR");
			
			exit(-1);
		}
	}
	
	printf("In main: exit!\n");
	pthread_exit(NULL);
	
	return 0;
}
           

運作效果圖:

Linux多線程程式設計---2

線程可以分為分離線程和非分離線程兩種:

分離線程:指線程退出時線程将釋放它的的資源的線程。

非分離線程:線程退出後不會立即釋放資源,需要另一個線程為它調用pthread_join函數或者程序退出時才會釋放資源。

總結:線程可以自己來設定分離也可以由其他線程來設定分離,函數int pthread_detach(pthread_t thread) 可以将非分離線程設定為分離線程。該函數成功傳回0,失敗傳回一個非0的錯誤碼.

demo2: 主線程建立了4個線程來進行數學運算,每個線程将運算結果使用pthread_exit()函數傳回給主線程,主線程使用pthread_join()等待4個線程完成和擷取線程的運作結果。

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<math.h>

#define Threads_number 4

void *BusyWork(void* t)//線程函數
{
	int i;
	long tid;
	double result = 0.0;
	tid = (long)t;
	
	printf("Thread %ld starting...\n", tid);
	for(i = 0; i < 1000000; i++)
	{
		result = result + sin(i)*tan(i);
	}
	
	printf("Thread %ld done,Result = %e\n", tid, result);
	
	pthread_exit((void*)t);//帶計算結果退出
}

int main(int argc, char* argv[])
{
	pthread_t threads[Threads_number];
	int rc;
	long t;
	void* status;
	
	for(t = 0; t < Threads_number; t++)
	{
	    printf("In main: creating thread %ld\n", t);
		//建立線程并傳入參數
		rc = pthread_create(&threads[t], NULL, BusyWork,(void*) t);
		
		if(rc)
		{
			printf("ERROR");
			
			exit(-1);
		}
		
	}
	
	for(t = 0; t < Threads_number; t++)
	{
		//以阻塞的方式等待thread[t]指定的線程結束,status為線程傳回值
		rc = pthread_join(threads[t], &status);
		
		if(rc)
		{
			printf("error");
			exit(-1);
		}
		
		printf("Main: completed join with thread %ld having a status of %ld\n", t, (long)status);
		
	}
	
	printf("In main: exit!\n");
	pthread_exit(NULL);
	
	return 0;
}
           

運作結果示例:

Linux多線程程式設計---2

線程的基本屬性包括:棧大小、排程政策和線程狀态

demo3: 下面的例程主要說明線程的建立以及線程屬性的使用方法,主線程根據參數清單的參數給出線程棧大小來設定線程屬性對象,然後為參數清單的剩餘參數分别建立線程來實作小寫轉大寫的功能及列印棧位址。

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

//出錯處理宏供傳回錯誤碼的函數使用
#define handle_error_en(en, msg) \
       do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

//出錯處理宏
#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct thread_info {   
   pthread_t thread_id;       
   int       thread_num;       
   char     *argv_string;     
};

//線程運作函數
static void *thread_start(void *arg)
{
   struct thread_info *tinfo = arg;
   char *uargv, *p;
   /*通過p的位址來計算棧的起始位址*/
   printf("Thread %d: top of stack near %p; argv_string=%s\n", tinfo->thread_num, &p, tinfo->argv_string);
   uargv = strdup(tinfo->argv_string);
   if (uargv == NULL)
       handle_error("strdup");

   for (p = uargv; *p != '\0'; p++)
       *p = toupper(*p);//将小寫字元轉換為大寫字元

   return uargv;//傳回字元串的起始位址
}
int main(int argc, char *argv[])
{
   int s, tnum, opt, num_threads;
   struct thread_info *tinfo;
   pthread_attr_t attr;
   int stack_size;
   void *res;

   stack_size = -1;
   while ((opt = getopt(argc, argv, "s:")) != -1) 
   {            /*處理參數-s所指定的棧大小*/
       switch (opt) 
	   {
		   case 's':
			   //“:”表示必須該選項帶有額外的參數,全域變量optarg會指向此額外參數
			   stack_size = strtoul(optarg, NULL, 0);
			   break;
		   default:
			   fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",argv[0]);
			   exit(EXIT_FAILURE);
       }
   }
   
   //全域變量optind訓示下一個要讀取的參數在argv中的位置。
   num_threads = argc - optind;
   
   s = pthread_attr_init(&attr);//初始化屬性對象
   
   if (s != 0)
       handle_error_en(s, "pthread_attr_init");
   if (stack_size > 0) 
   {
	   //設定屬性對象的棧大小
       s = pthread_attr_setstacksize(&attr, stack_size);
       if (s != 0)
           handle_error_en(s, "pthread_attr_setstacksize");
   }
   //在記憶體中動态地配置設定 num 個長度為 size 的連續空間,并将每一個位元組都初始化為 0
   tinfo = calloc(num_threads, sizeof(struct thread_info));
   
   if (tinfo == NULL)
       handle_error("calloc");
   
   for (tnum = 0; tnum < num_threads; tnum++) 
   {
       tinfo[tnum].thread_num = tnum + 1;
       tinfo[tnum].argv_string = argv[optind + tnum];
	   //根據屬性建立線程
       s = pthread_create(&tinfo[tnum].thread_id, &attr,&thread_start, &tinfo[tnum]);
       if (s != 0)
           handle_error_en(s, "pthread_create");
   }
   
   s = pthread_attr_destroy(&attr);//銷毀屬性對象
   
   if (s != 0)
       handle_error_en(s, "pthread_attr_destroy");

   for (tnum = 0; tnum < num_threads; tnum++) 
   {
	   //等待線程終止,并擷取線程傳回值
       s = pthread_join(tinfo[tnum].thread_id, &res);
       if (s != 0)
           handle_error_en(s, "pthread_join");
	   
       printf("Joined with thread %d; returned value was %s\n",tinfo[tnum].thread_num, (char *) res);
       free(res);      
   }

   free(tinfo);
   exit(EXIT_SUCCESS);
}
           

運作結果截圖:

Linux多線程程式設計---2

demo4: 使用互斥量來保證多線程同時輸出,然後通過互斥量保證擷取的線程列印完才讓别的線程列印

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

pthread_t tid[2];
pthread_mutex_t lock; //建立一個鎖變量

//線程1和線程2同時都要調用同一臨界資源 此處通過鎖互斥
void* doPrint(void *arg)
{
    int id = (long)arg;
    int i = 0;
    pthread_mutex_lock(&lock);//加鎖
    printf("Job %d started\n", id);
    for (i = 0; i < 5; i++) 
	{
	    printf("Job %d printing\n", id);
        usleep(10);
    }
    printf("Job %d finished\n", id);
    pthread_mutex_unlock(&lock);//釋放鎖
    return NULL;
}

int main(void)
{
    long i = 0;
    int err;
	
	//動态初始化鎖變量
    if (pthread_mutex_init(&lock, NULL) != 0)
    {
        printf("\n Mutex init failed\n");
        return 1;
    }
	
    while(i < 2)
    {
        err = pthread_create(&(tid[i]), NULL, &doPrint, (void*)i);
        if (err != 0)
            printf("Can't create thread :[%s]", strerror(err));
        i++;
    }
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
	
    pthread_mutex_destroy(&lock);//銷毀鎖

    return 0;
}
           

運作效果截圖:

Linux多線程程式設計---2

上面代碼中如果修改不使用互斥量,則可以看到輸出為亂序。

死鎖與避免

死鎖:指兩個或兩個以上的執行序在執行過程中因為争奪資源而造成的一種互相等待的現象。例如:一個線程T1已鎖定了一個資源R1,又想去鎖定資源R2,而此時另一個線程T2已鎖定了資源R2,卻去想鎖定資源R1,兩個線程都想得到對方的資源而不願釋放自己的資源,造成兩個線程都在等待,而無法執行的情況。

demo5: 示例死鎖發生的情況,程式建立了兩個線程,第一個線程先擷取mutexA鎖,再擷取mutexB鎖,第二個線程先擷取mutexB後擷取mutexA鎖,這時死鎖就可能發生。

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

pthread_t tid[2];
//靜态初始化互斥量
pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;

void * t1(void *arg) 
{
    pthread_mutex_lock(&mutexA);//線程1擷取mutexA鎖
    printf("t1 get mutexA\n");
    usleep(1000);
    pthread_mutex_lock(&mutexB);//線程1擷取mutexB鎖
    printf("t1 get mutexB\n");
    pthread_mutex_unlock(&mutexB);//線程1釋放mutexA鎖
    printf("t1 release mutexB\n");
    pthread_mutex_unlock(&mutexA);//線程1釋放mutexB鎖
    printf("t1 release mutexA\n");
    return NULL;
}

void * t2(void *arg) 
{
    pthread_mutex_lock(&mutexB);//線程2擷取mutexB鎖
    printf("t2 get mutexB\n");
    usleep(1000);
    pthread_mutex_lock(&mutexA);//線程2擷取mutexA鎖
    printf("t2 get mutexA\n");
    pthread_mutex_unlock(&mutexA);
    printf("t2 release mutexA\n");
    pthread_mutex_unlock(&mutexB);
    printf("t2 release mutexB\n");
    return NULL;
}

int main(void) 
{
    int err;
    long i;
    
	//建立一個線程 調用線程1函數	
    err = pthread_create(&(tid[0]), NULL, &t1, NULL );
    if (err != 0) 
        printf("Can't create thread :[%s]", strerror(err));
    
    err = pthread_create(&(tid[1]), NULL, &t2, NULL);
    if (err != 0)
        printf("Can't create thread :[%s]", strerror(err));

    for (i = 0; i < 2; i++) 
        pthread_join(tid[i], NULL);

    return 0;
}
           

運作截圖:

Linux多線程程式設計---2

程式會一直卡在這裡!發生了死鎖。

死鎖的避免:當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生,如果能確定所有的線程都是按照相同的順序獲得鎖,那麼死鎖就不會發生。比如規定程式内有三個互斥鎖的加鎖順序為mutexA->mutexB->mutexC,則線程t1,t2,t3線程操作僞代碼如下就不會出現死鎖:

t1                                          t2                                          t3

lock(mutexA)                        lock(mutexA)                         lock(mutexB)

lock(mutexB)                        lock(mutexC)                         lock(mutexC)

lock(mutexC)

繼續閱讀