Linux系统编程Day08

1.终端的概念

	进程中,控制终端是保存在PCB中的信息,而fork会复制PCB中的信息,因此由Shell
进程启动的其它进程的控制终端也是这个终端。
	默认情况下(没有重定向),每个进程的标准输入、标准输出和标准错误输出都指向
控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出
写也就是输出到显示器上。
	在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl+C表示SIGINT,
Ctrl+\表示SIGQUIT。

	函数说明:		
	#include <unistd.h>
	​
	char *ttyname(int fd);
	功能:由文件描述符查出对应的文件名
	参数:
	    fd:文件描述符
	返回值:
	    成功:终端名
	    失败:NULL
int main()
{
    printf("fd 0: %s\n", ttyname(0));
    printf("fd 1: %s\n", ttyname(1));
    printf("fd 2: %s\n", ttyname(2));return 0;
}

2.进程组概念

2.1 进程组概述
	进程组,也称之为作业。代表一个或多个进程的集合。
	每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作
系统设计的进程组的概念,是为了简化对多个进程的管理。
	当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID为
第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID为其进程ID
	可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死

	组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中
有一个进程存在,进程组就存在,与组长进程是否终止无关
	进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
	一个进程可以为自己或子进程设置进程组ID。

	相关函数说明
	#include <unistd.h>
	​
	pid_t getpgrp(void);                 /* POSIX.1 version */
	功能:获取当前进程的进程组ID
	参数:无
	返回值:总是返回调用者的进程组ID
	​
	pid_t getpgid(pid_t pid);
	功能:获取指定进程的进程组ID
	参数:
	    pid:进程号,如果pid = 0,那么该函数作用和getpgrp一样
	返回值:
	    成功:进程组ID
	    失败:-1
	​
	int setpgid(pid_t pid, pid_t pgid);
	功能:
	    改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。
	参数:
	    将参1对应的进程,加入参2对应的进程组中
	返回值:
	    成功:0
	    失败:-1

3. 会话

3.1 会话概念
	会话是一个或多个进程组的集合。

	1.一个会话可以有一个控制终端。这通常是终端设备或伪终端设备;
	2.建立与控制终端连接的会话首进程被称为控制进程;
	3.一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组;
	4.如果一个会话有一个控制终端,则它有一个前台进程组,其它进程组为后台进程组;
	5.如果终端接口检测到断开连接,则将挂断信号发送至控制进程(会话首进程)。

3.2 创建会话注意事项
	1) 调用进程不能是进程组组长,该进程变成新会话首进程(session header)		
	2) 若调用进程是组长进程,则出错返回		
	3) 该进程成为一个新进程组的组长进程		
	4) 需有root权限(ubuntu不需要)		
	5) 新会话丢弃原有的控制终端,该会话没有控制终端		
	6) 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
	
3.3 相关函数介绍
1.getsid函数:
	#include <unistd.h>		​
	pid_t getsid(pid_t pid);
	功能:获取进程所属的会话ID
	参数:
	    pid:进程号,pid为0表示查看当前进程session ID
	返回值:
	    成功:返回调用进程的会话ID
	    失败:-1
	组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

2.setsid函数:		
	#include <unistd.h>		​
	pid_t setsid(void);
	功能:
	    创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。调用了setsid函数的进程,既是新的会长,也是新的组长。
	参数:无
	返回值:
	    成功:返回调用进程的会话ID
	    失败:-1

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

//测试setsid和getsid
int main()
{	
	//获取当前进程组会话id
	
	pid_t pid = -1;
	pid = getsid(0);
	if(-1 == pid)
	{
		perror("getsid");
		return 1;
	}
	printf("sid: %d\n",pid);

	getchar();

	//创建一个会话
	//调用进程不能是组长进程,该进程会变成新的组长进程
	pid = setsid();
	if(-1 == pid)
	{
		perror("setsid");
		return 1;
	}
	printf("sid :%d\n", pid);

	return 0;
}

4. 守护进程

4.1守护进程介绍
	守护进程也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。
它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理
某些发生的事件。一般采用以d结尾的名字。
	守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱
离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在
任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每
一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制
终端,当控制终端被关闭时,相应的进程都会自动关闭。

4.2 守护进程模型
	1) 创建子进程,父进程退出(必须)		
	所有工作在子进程中进行形式上脱离了控制终端
	
	2) 在子进程中创建新会话(必须)		
	setsid()函数
	使子进程完全独立出来,脱离控制
	
	3) 改变当前目录为根目录(不是必须)		
	chdir()函数
	防止占用可卸载的文件系统
	也可以换成其它路径
	
	4) 重设文件权限掩码(不是必须)		
	umask()函数(一定是成功的)
	防止继承的文件创建屏蔽字拒绝某些权限
	增加守护进程灵活性
	
	5) 关闭文件描述符(不是必须)		
	继承的打开文件不会用到,浪费系统资源,无法卸载
	
	6) 开始执行守护进程核心工作(必须)	
	守护进程退出处理程序模型
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>

//创建守护进程
int main()
{
	pid_t pid = -1;
	int ret = -1;

	//1.创建子进程,父进程退出
	pid = fork();
	if(-1 == pid)
	{
		perror("fork");
		return 1;
	}
	
	if(pid > 0)
	{
		printf("父进程退出!");
		exit(0);
	}

	//2.创建新的会话,完全脱离控制终端
	pid = setsid();
	if(-1 == pid)
	{
		perror("setpid");
		return 1;
	}
	
	//3. 改变当前工作目录
	ret = chdir("/");
	if(-1 == ret)
	{
		perror("chdir");
		return 1;
	}

	//4.设置权限掩码
	umask(0);

	//5.关闭文件描述符
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	//6.执行核心任务
	//每间隔一秒钟将当前时间输入到目标文件中
	while(1)
	{
		system("date >> /tmp/txt.log");
		sleep(1);
	}

	return 0;
}

4.3 案例:后台生成文件,文件名字以当前时刻的时间命名
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<time.h>

int main()
{
	int ret = -1;
	pid_t pid = -1;
	char buffer[256];
	time_t t = -1;
	struct tm *pT = NULL;

	//1.创建子进程 关闭父进程
	pid = fork();
	if(-1 == pid)
	{
		return 1;
	}
	if(pid > 0)
	{
		exit(0);
	}
	//2.创建新会话
	pid = setsid();
	if(-1 == pid)
	{
		perror("setsid");
		return 1;
	}

	//3.改变当前工作目录
	ret = chdir("/");
	if(-1 == ret)
	{
		perror("chdir");
		return 1;
	}

	//4.改变文件掩码
	umask(0002);

	//5.关闭文件描述符
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	//6.执行任务
	while(1)
	{
		//获取当前时间 以秒为单位 从1970-01-01 00.00.00开始到现在秒数
		t = time(NULL);

		//转化为时间
		pT = localtime(&t);
		if(NULL == pT)
		{
			printf("localtime failed...\n");
			return 1;
		}
		//转化为文件名
		memset(buffer,0,1024);
		sprintf(buffer,"%s%d%d%d%d%d%d.log","touch /home/xujie/文档/",pT->tm_year + 1900, pT -> tm_mon + 1, pT->tm_mday, pT->tm_hour, pT->tm_min, pT->tm_sec);

		system(buffer);

		sleep(1);
	}

	return 0;
}

5.线程简介

5.1 线程概念
	在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什
么, 只是维护应用程序所需的各种资源,而线程则是真正的执行实体。
	所以,线程是轻量级的进程(LWP:light weight process),在Linux环境下
线程的本质仍是进程。
	为了让进程完成一定的工作,进程必须至少包含一个线程,因为需要线程完成相应的功能。
	进程,直观点说,保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内
存体,这个内存体有自己的地址空间,有自己的堆,上级挂靠单位是操作系统。操作系
统会以进程为单位,分配系统资源,所以我们也说,进程是CPU分配资源的最小单位:
	1.线程存在与进程当中(进程可以认为是线程的容器),是操作系统调度执行的最小单位。
	2.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进
行资源分配和调度的一个独立单位。
	3.线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立
运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资
源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进
程所拥有的全部资源。 

	即:进程是操作系统分配资源的最小单位
	   线程是操作系统调度的最小单位

5.2 线程的特点
	进程和线程关系密切:
	1) 线程是轻量级进程(light-weight process),也有PCB,创建线程使用的
	底层函数和进程一样,都是clone
	2) 从内核里看进程和线程是一样的,都有各自不同的PCB.
	3) 进程可以蜕变成线程
	4) 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
	实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数 clone 。		
		1.如果复制对方的地址空间,那么就产出一个“进程”;		
		2.如果共享对方的地址空间,就产生一个“线程”。		
		3.即如果clone底层实现的是一个类似于浅拷贝的操作,那么就产生一个线程,
		如果实现的是一个类似于深拷贝的操作,那么就产生一个进程。
		
	Linux内核是不区分进程和线程的, 只在用户层面上进行区分。所以,线程所有操作
函数 pthread_* 是库函数,而非系统调用。

5.3 线程的资源
	1. 线程共享资源
		1) 文件描述符表		
		2) 每种信号的处理方式		
		3) 当前工作目录
		4) 用户ID和组ID		
		内存地址空间 (.text/.data/.bss/heap/共享库)
		
	2. 线程非共享资源
		1) 线程id		
		2) 处理器现场和栈指针(内核栈)		
		3) 独立的栈空间(用户空间栈)		
		4) errno变量		
		5) 信号屏蔽字		
		6) 调度优先级

5.4 线程的优缺点
	优点:		
		1.提高程序并发性		
		2.开销小		
		3.数据通信、共享数据方便

	缺点:		
		1.库函数,不稳定			
		2.调试、编写困难、gdb不支持			
		3.对信号支持不好

	优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是
	很大。

5.5 线程常用操作
 1.线程号
	就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中
是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。		
	进程号用 pid_t 数据类型表示,是一个非负整数。线程号则用 pthread_t 数据
类型来表示,Linux 使用无符号长整数表示。		
	有的系统在实现pthread_t 的时候,用一个结构体来表示,所以在可移植的操作
系统实现不能把它做为整数处理。
			
	pthread_self函数:		
		#include <pthread.h>		​
		pthread_t pthread_self(void);
		功能:
		    获取线程号。
		参数:
		    无
		返回值:
		    调用线程的线程 ID 。		  		 
	
	pthread_equal函数:		
		int pthread_equal(pthread_t t1, pthread_t t2);
		功能:
		    判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
		参数:
		    t1,t2:待判断的线程号。
		返回值:
		    相等:  非 0
		    不相等:0
注:线程函数的程序在 pthread 库中,故链接时要加上参数 -lpthread(gcc test.c -pthread),
以此生成可执行文件.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>

//线程常用函数
int main()
{
	pthread_t tid = 0;
       	//获取当前线程的线程号
	tid = pthread_self();

	printf("tid :%lu\n", tid);

	//比较两个线程id是否相同
	if(pthread_equal(tid, pthread_self()))
	{
		printf("两个线程id相同\n");
	}
	else
	{
		printf("俩个线程id不同\n");
	}

	return 0;
}
2.线程的创建
pthread_create函数:	
	#include <pthread.h>
	​
	int pthread_create(pthread_t *thread,
	            const pthread_attr_t *attr,
	            void *(*start_routine)(void *),
	            void *arg );
	功能:
	    创建一个线程。
	参数:
	    thread:线程标识符地址。
	    attr:线程属性结构体地址,通常设置为 NULL。
	    start_routine:线程函数的入口地址。
	    arg:传给线程函数的参数。
	返回值:
	    成功:0
	    失败:非 0
	在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()
返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针
start_routine决定。	
	由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打
印错误信息,可以先用strerror()把错误码转换成错误信息再打印。

例子1:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>

int num = 100;

//线程处理函数
void* fun(void* arg)
{
	int *pn = (int* )arg;

	printf("之前的 num = %d *pn = %d\n",num, *pn);
	num++;
	(*pn)++;
	printf("之后的 num = %d *pn = %d\n", num, *pn);
	return NULL;
}

//数据段和堆
int main()
{
	int ret = -1;

	pthread_t tid;

	memset(&tid, 0, sizeof(tid));

	//分配堆空间
	int* p = malloc(sizeof(int));
	if(NULL == p)
	{
		printf("开辟堆空间失败!");
		return 0;
	}
	memset(p, 0, sizeof(int));
	*p = 90;

	//创建一个线程
	//ret = pthread_create(&tid, NULL, fun, NULL);
	ret = pthread_create(&tid, NULL, fun, p);
	if(0 != ret)
	{
		printf("线程创建失败!\n");
		return 0;
	}

	printf("按下任意键 函数继续\n");
	getchar();
	printf("main num = %d *p = %d\n", num, *p);
	
	if(NULL != p)
	{
		free(p);
		p = NULL;
	}
	return 0;
}

例子2:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>

//线程执行任务
void* fun(void* arg)
{
	printf("新的线程执行任务 tid: %lu\n",pthread_self());
	return NULL;
}

void* fun1(void* arg)
{
	int var = (int)(long)arg;
	printf("第二个线程执行任务,var = %d\n",var);
	return NULL;
}

//创建一个线程
int main()
{
	pthread_t tid = -1;
	pthread_t tid1 = -1;
	int ret = -1;

	//创建一个线程
	ret = pthread_create(&tid, NULL, fun, NULL);
	if(0 != ret)
	{
		printf("线程创建失败!\n");
		return 1;
	}

	//创建第二个线程
	ret = pthread_create(&tid1, NULL, fun1, (void* )0x3);
	if(0 != ret)
	{
		printf("线程创建失败!\n");
		return 1;
	}

	printf("线程tid :%lu\n",pthread_self());

	printf("按下任意键主线程退出\n");
	getchar();

	return 0;
}

3.线程资源回收
pthread_join函数:		
	#include <pthread.h>		​
	int pthread_join(pthread_t thread, void **retval);
	功能:
	    等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
	参数:
	    thread:被等待的线程号。
	    retval:用来存储线程退出状态的指针的地址。
	返回值:
	    成功:0
	    失败:非 0

	调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同
的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
	1) 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程
函数的返回值。
	2) 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向
的单元里存放的是常数PTHREAD_CANCELED。
	3) 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放
的是传给pthread_exit的参数。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

//线程执行函数
void* fun(void* arg)
{
	printf("新创造的线程开始执行\n");
	for(int i = 0; i < 5; i++)
	{
		printf("线程正在持续工作中!\n");
		sleep(1);
	}

	return (void* )0x3;
}

//回收线程的资源
int main()
{
	int ret = -1;
	void* p = NULL;
	pthread_t tid = -1;
	
	//创建一个线程
	ret = pthread_create(&tid, NULL, fun, NULL);
	if(ret != 0)
	{
		printf("创建线程出现错误!\n");
		return 0;
	}

	printf("主线程正在执行\n");

	//等待线程结束 函数会阻塞
	ret = pthread_join(tid, &p);
	if(0 != ret)
	{
		printf("线程回收失败!\n");
		return 1;
	}
	printf("p:%p\n", p);
	printf("主线程退出!\n");

	return 0;
}

4. 案例:多线程输出大小写字母
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

//输出大写字母
void *fun1(void* arg)
{
	for(int i = 'A';i <= 'Z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}
	return NULL;
}

//输出小写字母
void *fun2(void* arg)
{
	for(int i = 'a';i <= 'z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}
	return NULL;
}

//模拟输出字母
int main()
{
	pthread_t tid1,tid2;

	//创建两个线程
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_create(&tid2, NULL, fun2, NULL);

	//等待线程结束
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);

	putchar('\n');
	printf("主进程结束\n");

	return 0;
}

5.线程分离
	一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取
它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它
占用的所有资源,而不保留终止状态。		
	不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL
错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
					
	pthread_detach函数:
			#include <pthread.h>		​
			int pthread_detach(pthread_t thread);
	功能:
	    使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
	参数:
	    thread:线程号。
	返回值:
	    成功:0
	    失败:非0
	    
6.线程退出
	在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以
通过以下三种在不终止整个进程的情况下停止它的控制流。	
	1.线程从执行函数中返回。
	2.线程调用pthread_exit退出线程。
	3.线程可以被同一进程中的其它线程取消。
 

pthread_exit函数:		
	#include <pthread.h>		​
	void pthread_exit(void *retval);
	功能:
	    退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
	参数:
	    retval:存储线程退出状态的指针。
	返回值:无  
		    
7.线程取消
	#include <pthread.h>	​
	int pthread_cancel(pthread_t thread);
	功能:
	    杀死(取消)线程
	参数:
	    thread : 目标线程ID。
	返回值:
	    成功:0
	    失败:出错编号
​
注:线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。
	杀死线程也不是立刻就能完成,必须要到达取消点。	
	取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统
调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads
可以查看具备这些取消点的系统调用列表。	
	可粗略认为一个系统调用(进入内核)即为一个取消点。
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>

//线程执行任务
void* fun(void* arg)
{
	for(int i = 0;i < 5; i++)
	{
		printf("线程正在工作\n");
		sleep(1);
	}
	
	//return NULL;
	//等价于return
	pthread_exit(NULL);

	//终止整个进程
	//exit(0);

}


//创建一个线程
int main()
{
	pthread_t tid = -1;

	int ret = -1;

	//创建一个线程
	ret = pthread_create(&tid, NULL, fun, NULL);
	if(0 != ret)
	{
		printf("线程创建失败!\n");
		return 1;
	}


	printf("线程tid :%lu\n",pthread_self());

	//设置线程分离
	ret = pthread_detach(tid);
	if(0 != ret)
	{
		printf("线程分离失败\n");
		return 1;
	}
	
	sleep(3);
	printf("3秒后主线程杀死子线程");
	pthread_cancel(tid);

	return 0;
}

8. 线程属性
	1. 概述
		Linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都
	是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。
		如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线
	程栈的大小来降低内存的使用,增加最大线程个数。
	
	typedef struct
	{
	    int             etachstate;     //线程的分离状态
	    int             schedpolicy;    //线程调度策略
	    struct sched_param  schedparam; //线程的调度参数
	    int             inheritsched;   //线程的继承性
	    int             scope;      //线程的作用域
	    size_t          guardsize;  //线程栈末尾的警戒缓冲区大小
	    int             stackaddr_set; //线程的栈设置
	    void*           stackaddr;  //线程栈的位置
	    size_t          stacksize;  //线程栈的大小
	} pthread_attr_t;
	
	主要结构体成员:	
		1) 线程分离状态		
		2) 线程栈大小(默认平均分配)		
		3) 线程栈警戒缓冲区大小(位于栈末尾)		
		4) 线程栈最低地址


		属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,
	这个函数必须在pthread_create函数之前调用。之后须用pthread_attr_destroy
	函数来释放资源。

		线程属性主要包括如下属性:作用域(scope)、栈尺寸(stack size)、
	栈地址(stack address)、优先级(priority)、分离的状态(detached state)、
	调度策略和参数(scheduling policy and parameters)。默认的属性为非
	绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。

	2 线程属性初始化和销毁函数
	#include <pthread.h>
	
	​属性初始化
	int pthread_attr_init(pthread_attr_t *attr);
	功能:
	    初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程
	参数:
	    attr:线程属性结构体
	返回值:
	    成功:0
	    失败:错误号
	    
	​属性销毁函数
	int pthread_attr_destroy(pthread_attr_t *attr);
	功能:
	    销毁线程属性所占用的资源函数
	参数:
	    attr:线程属性结构体
	返回值:
	    成功:0
	    失败:错误号
	    
	3 线程分离状态
	线程的分离状态决定一个线程以什么样的方式来终止自己。

		非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创
	建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释
	放自己占用的系统资源。
		分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终
	止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
	
	相关函数:

	#include <pthread.h>
	​
	int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
	功能:设置线程分离状态
	参数:
	    attr:已初始化的线程属性
	    detachstate:    分离状态
	        PTHREAD_CREATE_DETACHED(分离线程)
	        PTHREAD_CREATE_JOINABLE(非分离线程)
	返回值:
	    成功:0
	    失败:非0
	    
​	int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
	功能:获取线程分离状态
	参数:
	    attr:已初始化的线程属性
	    detachstate:    分离状态
	        PTHREAD_CREATE_DETACHED(分离线程)
	        PTHREAD _CREATE_JOINABLE(非分离线程)
	返回值:
	    成功:0
	    失败:非0
​
		这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,
	它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和
	系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的
	线程号。	
		要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的
	线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时
	间让函数pthread_create返回。
		设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如
	wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

	4 线程栈地址
	POSIX.1定义了两个常量来检测系统是否支持栈属性:
		_POSIX_THREAD_ATTR_STACKADDR
		_POSIX_THREAD_ATTR_STACKSIZE
	也可以给sysconf函数传递来进行检测:
		_SC_THREAD_ATTR_STACKADDR
		_SC_THREAD_ATTR_STACKSIZE
		
		当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自
	己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个
	函数分别设置和获取线程的栈地址。

	#include <pthread.h>
	​
	int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,  size_t stacksize);
	功能:设置线程的栈地址
	参数:
	    attr:指向一个线程属性的指针
	    stackaddr:内存首地址
	    stacksize:返回线程的堆栈大小
	返回值:
	    成功:0
	    失败:错误号
	​
	int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr,  size_t *stacksize);
	功能:获取线程的栈地址
	参数:
	    attr:指向一个线程属性的指针
	    stackaddr:返回获取的栈地址
	    stacksize:返回获取的栈大小
	返回值:
	    成功:0
	    失败:错误号

	5 线程栈大小
		当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地
	址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,
	可能需要增大线程栈的默认大小。

	#include <pthread.h>
	​
	int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
	功能:设置线程的栈大小
	参数:
	    attr:指向一个线程属性的指针
	    stacksize:线程的堆栈大小
	返回值:
	    成功:0
	    失败:错误号
	​
	int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
	功能:获取线程的栈大小
	参数: 
	    attr:指向一个线程属性的指针
	    stacksize:返回线程的堆栈大小
	返回值:
	    成功:0
	    失败:错误号
	​
	6 线程使用注意事项
		1) 主线程退出其他线程不退出,主线程应调用pthread_exit
		2) 避免僵尸线程		
			a) pthread_join		
			b) pthread_detach		
			c) pthread_create指定分离属性
		3)被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应
		当返回被回收线程栈中的值;
		4) malloc和mmap申请的内存可以被其他线程释放
		5) 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的
		线程存在,其他线程t在子进程中均pthread_exit
		6) 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

	7 综合参考程序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>

//线程执行任务
void* fun(void* arg)
{
	for(int i = 0;i < 5; i++)
	{
		printf("线程正在工作\n");
		sleep(1);
	}
	
	//return NULL;
	//等价于return
	pthread_exit(NULL);
}


//创建一个线程
int main()
{
	pthread_t tid = -1;
	pthread_attr_t attr;
	int ret = -1;
	//初始化线程属性
	ret = pthread_attr_init(&attr);
	if(0 != ret)
	{
		printf("初始化线程属性失败!\n");
		return 1;
	}
	printf("初始化线程属性成功!\n");

	//设置线程属性为分离状态
	ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	if(0 != ret)
	{
		printf("线程属性设置失败!");
		return 1;
	}


	//创建一个线程
	//非分离状态
	ret = pthread_create(&tid, NULL, fun, NULL);
	//分离状态
	//ret = pthread_create(&tid, &attr, fun, NULL);
	if(0 != ret)
	{
		printf("线程创建失败!\n");
		return 1;
	}

	//处于分离状态的线程不能被join,因此使用该方法测试一下
	ret = pthread_join(tid, NULL);
	if(0 != ret)
	{
		printf("join失败,此时是分离状态!\n");
	}
	else
	{
		printf("当前线程是非分离状态!\n");
	}

	//销毁线程属性
	ret = pthread_attr_destroy(&attr);
	if(0 != ret)
	{
		printf("销毁线程属性失败\n");
		return 1;
	}
	
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值