天天看點

linux多線程程式設計:建立和取消

線程

  1. 線程是程序的一個執行流,是CPU排程和分派的基本機關,它是比程序更小的能獨立運作的基本機關。
  2. 一個程序由幾個線程組成(擁有很多相對獨立的執行流的使用者程式共享應用程式的大部分資料結構),線程與同屬一個程序的其他的線程共享程序所擁有的全部資源。
  3. 程序——資源配置設定的最小機關,線程——程式執行的最小機關"
  4. 程序有獨立的位址空間,一個程序崩潰後,在保護模式下不會對其它程序産生影響,而線程隻是一個程序中的不同執行路徑。
  5. 線程有自己的堆棧和局部變量,但線程沒有單獨的位址空間,一個線程死掉就等于整個程序死掉,是以多程序的程式要比多線程的程式健壯,但在程序切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發操作,隻能用線程,不能用程序。

一個程序中線程共享的資源:

  1. 可執行的指令(代碼)
  2. 靜态資料(一般指的是全局變量)
  3. 程序中打開的檔案描述符
  4. 目前的工作目錄
  5. 使用者ID
  6. 使用者組ID

每個線程私有的資源包括:

  1. 線程ID(TID)
  2. PC(程式計數器)和相關寄存器 (每個線程就可以執行自己的代碼)
  3. 堆棧(棧)
  4. 錯誤号(errno)
  5. 優先級
  6. 執行狀态和屬性

多線程程式設計

多線程需要用到pthread線程庫 p是POSIX,即遵循POSIX标準。這裡函數執行成功傳回0,失敗傳回錯誤碼(和c庫函數有所不同,失敗傳回-1,成功錯誤碼另行判斷)

  • int pthread_create(pthread_t * tidp,const pthread_attr_t*attr,void*(*start_rtn)(void),void*arg)

    第一個參數 tidp:是線程ID,線程建立成功時tidy指向的記憶體單元被設定為新建立線程的ID(解惑:在使用時直接定義pthread_t型資料傳入建立函數中不是傳的任意值,而是這個變量用于存放即将建立的線程的ID,傳入的是指針,是以傳的時候取位址)

    第二個參數 attr:定制不同的線程屬性 通常被設定為NULL

    第三個參數 start_rtn:從類型定義上看是函數指針,傳入的是這個函數的指針。新建立額線程就在這個函數中運作。

    第四個參數 arg:start_rtn函數的參數,無參數時設定為NULL即可。有參數時輸入參數的位址,而多個參數可以用結構體傳遞。(注意:類型是void型,是以傳參時要用強轉)

  • void pthread_exit(void *retval)

    參數:retval表示線程退出狀态,通常傳NULL。

    注意點:

    1:return的作用是傳回函數的調用點,線上程函數中和pthread_exit的作用等價。但是main函數中的return則代表主線程的結束即整個程序結束,釋放位址空間,所有線程都會終止。而exit和_exit函數也會終止整個程序。

    2、pthread_exit或者return傳回的指針(線程執行函數用return或者pthread_exit結束線程時),所指向的記憶體單元必須是全局的或者是malloc配置設定的,簡而言之,就是變量不能是在函數中定義的,因為結束線程後這個線程函數的棧空間就會被重新配置設定出去,這樣其他線程的傳回指針是無意義的。

  • int pthread_join(pthread_t tid,void **rval_ptr)

    參數1 tid:等待退出線程的ID

    參數2 rval_ptr:線程退出的傳回值的指針(就是pthread_exit函數傳回值的指針)

    在指定線程線程退出前程式會一直阻塞在這裡。

  • int pthread_cancel(pthread_t thread)

    用于結束一個指定的線程,相對于使用pthread_exit 函數的主動退出,該函數的使用屬于使線程被動結束。

    在使用該函數時的注意點:會出現線程無法被取消的情況是因為在收到取消程序請求後在

    預設情況下

    線程并不會立刻結束,因為它僅僅隻是一個請求。而是繼續向下走如果遇到取消點才會取消。是以沒有被取消就是因為沒有設定取消點。

    前面說了是在預設情況下,收到取消請求後,線程将會運作到函數内部的取消點後終止函數。這就要涉及另外相關的函數:

    1、

    int pthread_setcancelstate(int state, int *oldstate)

    設定本線程對Cancel信号的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(預設)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号後設為CANCLED狀态和忽略CANCEL信号繼續運作;old_state如果不為 NULL則存入原來的Cancel狀态以便恢複。

    2、

    int pthread_setcanceltype(int type, int *oldtype)

    設定本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFERRED和 PTHREAD_CANCEL_ASYNCHRONOUS,僅當Cancel狀态為Enable時有效,分别表示收到信号後繼續運作至下一個取消點再退出和 立即執行取消動作(退出);oldtype如果不為NULL則存入原來的取消動作類型值。

    此函數應該線上程開始時執行,若線程内部有任何資源申請等操作,應該選擇 PTHREAD_CANCEL_DEFERRED 的設定,然後在退出點(pthread_testcancel 用于定義退出點)進行線程退出。

    3、

    void pthread_testcancel(void)

    檢查本線程是否處于Canceld狀态,如果是,則進行取消動作,否則直接傳回。 此函數線上程内執行,執行的位置就是線程退出的位置,在執行此函數以前,線程内部的相關資源申請一定要釋放掉,他很容易造成記憶體洩露。

    關于取消點的了解,posix提供一系列自帶取消點的函數,而大部分函數的特點是阻塞式的系統調用或者較大花費的系統調用,使得作業系統陷入核心态,而我們知道系統在從核心态切回使用者态時會進行一個信号的檢查,如果收到信号就會處理信号,而取消信号應該就是在此時被處理,進而取消掉線程。

    (而我在寫相關代碼測試時會用到printf列印顯示代碼運作過程發現結果與預期不同,後來發現printf也是屬于系統調用函數 ,在printf裡面是用的putc函數輸出,繼續跟蹤putc()發現write函數被調用,繼續跟蹤write函數,聲明是在user.h檔案中但是沒有具體c代碼的實作,這就是一個系統調用)

    參考連結: 【Linux多線程】pthread_cancel函數

    參考連結:pthread_cancel為何無法取消掉一個線程

相關代碼

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

pthread_t tid[3];

void delay()    //為了與sleep比較,因為sleep算系統調用
{
	int x = 10000, y;
	while (x > 0)
	{
		y = 30000;
		while (y > 0)
		{
			y--;
		}
		x--;
	}
}

void *mythread1(void *arg)
{
	int i = 5;
	while (i--)
	{
		printf("this is mythread1\n");
		//sleep(1);
		delay();
	}

	pthread_exit((void *)10);    //線程退出
}

void *mythread2(void *arg)
{
	int old;
	pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old);  //立即取消  old儲存原來的屬性
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);  //不允許被取消 old儲存原來的屬性
	int i = 5;
	while (i--)
	{
		if(i == 3)
		{
			printf("%s,%d\n", (char *)arg,i);
		}
		//usleep(500000);
		delay();
	}
}

struct Test
{
	char name[32];
	int a;
	char b;
};

void *mythread3(void *arg)
{
	struct Test *t = (struct Test *)arg;
	printf("%s %d %c\n", t->name, t->a, t->b);
	pthread_cancel(tid[1]);   //取消線程1  預設屬性 線程運作到下一個取消點的時候被取消
}

int main()
{
	int ret;
	ret = pthread_create(&tid[0], NULL, mythread1, NULL);   //線程号 預設屬性  線程函數   不需要傳參
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	ret = pthread_create(&tid[1], NULL, mythread2, "helloworld");
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	struct Test t = {"aaa", 10, 'x'};
	ret = pthread_create(&tid[2], NULL, mythread3, (void *)&t);
	if (ret != 0)
	{
		perror("pthread_create");
		exit(1);
	}

	void *status;
	pthread_join(tid[0], &status);   //1、等待線程結束  2、回收線程資源
	printf("線程1退出,退出碼是%d\n", (int)status);
	pthread_join(tid[1], &status);
	pthread_join(tid[2], &status);

	return 0;
}
           

繼續閱讀