Linux 线程操作函数技能总结

线程概念

    线程是指运行中的程序的调度单位。一个线程指的是进程中一个单一顺序的控制流,也被称为轻量级线程。它是系统独立调度和分配的基本单位。同一进程中的多个线程将共享该系统中的全部系统资源,比如文件描述符和信号处理等。一个进程可以有很多线程,每个线程并行执行不同的任务。

线程与进程比较

   ①  和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统中,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护其代码段、堆栈段和数据段,这种多任务工作方式的代价非常“昂贵”。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且线程间彼此切换所需要时间也远远小于进程间切换所需要的时间。
   ②  线程间方便的通信机制。对不同进程来说它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行。这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,不仅方便,而且快捷。

   Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread。例如:gcc  filename  -lpthread。注意,这里要讲的线程相关操作都是用户空间中的线程的操作。

 线程创建:

创建线程实际上就是确定调用该线程函数的入口点,这里通常使用的函数是pthread_create()。在线程创建后,就开始运行相关的线程函数。


与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线 程)同样的执行序列,而是使其运行start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性 (见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取。

线程创建属性

pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括以下几项:
__detachstate, 表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为 PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为 PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到 PTHREAD_CREATE_JOINABLE状态。
__schedpolicy,表示新线程的调度策略,主要包括 SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。
__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR 或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。
__inheritsched, 有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和 调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
__scope, 表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值: PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同 进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。
为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_attr_set---函数。

线程创建的Linux实现

我 们知道,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用__clone()和fork (),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因 此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文 件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统 调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来创建线程时,则最终设置了所 有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的"进程"拥有共享的运行环境,只有栈是独立的,由 __clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删 除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号 (比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。

实验1

   功能:使用pthread_create()函数创建线程的实例
   代码:thread_create.c文件

/*thread_create.c*/
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

/*线程函数1*/
void *nythreadl(void)
{
	int i;
	for(i=0; i<5; i++) {
		prlntf("I am the 1st pthread, created by mybeilef321\n");
		sleep(2);
	}
}
/*线程函数2*/
void *mythread2(void)
{
	int i;
	for(i=0; l<5; l++) {
		prlntf("I am the 2st pthread, created by mybelief321\n");
		sleep(2);
	}
}

int main()
{
	pthread_t id1, id2;/*线程ID*/
	intres;
	/*创建一个线程,并使得该线程执行mythreadl函数*/
	res=pthread_create(&id1, NULL, (void *)mythread1, NULL);
	if(res) {
		prlntf("Createpthreaderror!\n");
		return 1;
	}
	/*创建一个线程,并使得该线程执行mythread2函数*/
	res=pthread_create(&id2, NULL, (void *)mythread2, NULL);
	if(res) {
		prlntf("Createpthreaderror!\n");
		return 1;
	}
	/*等待两个线程均推出后,main()函数再退出*/
	pthread_join(id1, NULL);
	pthread_join(id2, NULL);

	return 1;
}
编译:使用命令:gcc thread_create.c -o thread_create -lpthread编译,注意不要忘了加 -lpthread,否则会出现如下的错误
/tmp/ccjfZIN3.o: In function `main':
thread_create.c:(.text+0x8b): undefined reference to `pthread_create'
thread_create.c:(.text+0xc0): undefined reference to `pthread_create'
thread_create.c:(.text+0xeb): undefined reference to `pthread_join'
thread_create.c:(.text+0xfc): undefined reference to `pthread_join'
collect2: ld returned 1 exit status
   执行:

song@ubuntu:~/llanxi$ vim thread_create.c
song@ubuntu:~/llanxi$ gcc thread_create.c -o thread_create -I pthread
song@ubuntu:~/llanxi$ ./thread_create
I am the 2st pthread, created by mybellef321
I am the 1st pthread, created by mybeilef321
I am the 2st pthread, created by mybellef321
I am the 1st pthread, created by mybeilef321
I am the 2st pthread, created by mybellef321
I am the 1st pthread, created by mybeilef321
I am the 2st pthread, created by mybellef321
I am the 1st pthread, created by mybeilef321
I am the 2st pthread, created by mybellef321
I am the 1st pthread, created by mybeilef321

  线程退出:

在线程创建后,就开始运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出的一种方法。另一种退出线程的方法是使用函数pthread_exit(),这是线程的主动行为。这里要注意的是,在使用线程函数时,不能随意使用exit()退出函数来进行出错处理。由于exit()的作用是使调用进程终止,而一个进程往往包含多个线程,因此,在使用exit()之后,该进程中的所有线程都终止了。在线程中就可以使用pthread_exit()来代替进程中的exit()。

实验2

  功能:使用pthread_exit()函数退出线程的举例
  代码:thread_exit.c文件

/*thread_exit.c文件*/
#include<stdio.h>
#include<pthread.h>
/*进程函数*/
void *create(void *arg)
{
	printf("New thread is created...\n");
	pthread_exit((void *)6);/*这里的6也可以设置成其它数值*/
}

int main()
{
	pthread_t tid;
	int res;
	void *tenp;
	res = pthread_create(&tid, NULL, create, NULL);
	prlntf("I am the main thread!\n");
	if(res) {
		printf("thread is not created...\n");
		return -1;
	}

	res = pthread_join(tid, &tenp);
	if(res) {
		prlntf("Thread is not exit...\n");
		return -2;
	}
	prlntf("Thread is exit code %d \n",(int)temp);
	return 0;
}
编译:gcc thread_exit.c -o thread_exit -lpthread
  执行:./thread_exit
song@ubuntu:~/llanxi$ ./thread_exlt
I am the main thread!
New thread is created…
Thread is exit code6

  线程等待:

由于一个进程中的多个线程是共享数据段的,因此,通常在线程退出后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join()用于将当前进程挂起来等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。

实验3

  功能:用pthread_join()实现线程等待。
  代码:thread_join.c文件

/*thread_join.c*/
#include<pthread.h>
#include<stdio.h>
/*线程函数*/
void *thread(void *str)

{

	int i;
	for(i=0; i<4; ++i) {
		sleep(2);
		prlntf("This is the thread:%d\n",i);
	}
	return NULL;
}

int main()
{
	pthiead_t pth;/*线程ID*/
	int i;
	int ret=pthread_create(&pth,NULL,thread,(void *)(i));
	        pthread_join(pth,NULL);
	printf("123\n");
	for(i=0; i<3; ++i) {
		sleep(1);
		prlntf("This is the main:%d\n",i);
	}
	return 0;
}
编译:gcc thread_join.c -o thread_join -lpthread  
  执行:./thread_join
songubuntu : —/Uanxt$ . /thread_jotn
This is the thread:O
This ts the thread:1
This ts the thread:2
This is the thread:3
123
This is
This is
This is
the main:O the main:1 the main:2
可以看出,pthread_join()等到线程结束后,程序才继续执行。

  线程取消:

前面已经提到线程调用pthread_exit()函数主动终止自身线程,但是在很多线程应用中,经常会遇到在别的线程中要终止另一个线程的问题,此时调用pthread_cancel()函数来实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态。例如,被取消的线程接收到另一个线程的取消请求之后,是接受函数忽略这个请求;如果是接受,则再判断立刻采取终止操作还是等待某个函数的调用等。

线程取消的语义

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。

 取消点

根 据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系 统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手 册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻 塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用 pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:
pthread_testcancel();
     retcode = read(fd, buffer, length);
     pthread_testcancel();

程序设计方面的考虑

如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。

与线程取消相关的pthread函数

int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
int pthread_setcancelstate(int state, int *oldstate)
设 置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为 NULL则存入原来的Cancel状态以便恢复。
int pthread_setcanceltype(int type, int *oldtype)
设 置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和 立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。
void pthread_testcancel(void)
检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回。


  线程标识符获取:

获取调用线程的标识ID。 


实验4

  功能:使用pthread_self()获取线程ID
  代码:thread_id.c文件

编译:gcc thread_id.c -o thread_id -lpthread
  执行:./thread_id

   线程清除:

线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。
  从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。



实验5

  功能:线程清理函数的使用
  代码:thread_clean.c




   注意,在编写的代码的时候,自己修改一下传递的参数和clean_pop函数的参数,相信你会更有收获。
  编译:gcc thread_clean.c -o thread_clean -lpthread
  执行:./thread_clean

分离释放线程

int pthread_detach(pthread_t thread); 返回值:若是成功返回0,否则返回错误的编号 形 参: thread 要释放线程的标识符ID 说 明:linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态。
一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。如果线程状态为joinable,需要在之后适时调用pthread_join。
头文件:#include <pthread.h>

比较两个线程是否是同一个线程

int pthread_equal(pthread_t thread1, pthread_t thread2); 返回值:若是返回0 不相等,非零相等 形 参: thread1 要比较的线程的标识符ID thread2 要比较的线程的标识符ID 说 明:判断两个线程ID是否相等。 头文件:#include <pthread.h>

线程操作要注意的几个小问题:

1. pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则将产生奇怪的结果

实验6

功能:本实验创建了3个进程,为了更好的描述线程之间的并行执行,让3个线程共用同一个执行函数。每个线程都有5次循环(可以看成5个小任务),每次循环之间会随机等待1~10s的时间,意义在于模拟每个任务的到达时间是随机的,并没有任何特定的规律。
代码:thread.c文件


编译:gcc thread.c -o thread -lpthread
执行:./thread



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值