Linux系统编程与网络编程——Linux多线程介绍,线程的创建与使用,线程属性(十五)

Linux多线程介绍

多线程是在同一时间需要完成多项任务的时候实现的。

  1. 轻量级进程(light-weight process),每个线程也有自己与进程控制表和 PCB 相似的线程控制表 TCB ,而这个TCB 中所保存的线程状态信息则要比 PCB 表少得多,这些信息主要是相关栈指针(系统栈和用户栈),寄存器中的状态数据
  2. 从内核里看进程和线程是一样的,都有各自不同的PCB或者TCB,但是PCB或者TCB中指向内存资源的三级页表
    是相同的。
  3. 进程可以蜕变成线程。
  4. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
    在这里插入图片描述
    在这里插入图片描述
    线程间共享资源
    在这里插入图片描述
    在这里插入图片描述
    线程间非共享资源
    在这里插入图片描述
    线程优点
    在这里插入图片描述
    线程缺点
    在这里插入图片描述
    man帮助
    查看manpage关于pthread的函数
    在这里插入图片描述
    安装pthread相关manpage
    在这里插入图片描述

线程的创建与使用

创建线程

用pthread_create()函数,其函数原型是:
在这里插入图片描述
第一个参数为指向线程标识符的指针,第二个参数用到设置线程属性,分配多少的栈空间,以及运行优先级,大多数情况下,我们不需要设置线程的属性,那么空指针NULL就可以了,第三个参数是线程运行函数的起始地址,最后一个参数是传递给线程的参数。

当创建线程成功时,函数返回0,失败返回错误码。不会设置errno。

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。

start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。

start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,类似于父进程调用wait(2)得到子进程的退出状态。

pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调pthread_self(3)可以获得当前线程的id。
在这里插入图片描述
Linux中多线程的实现可以使用pthread线程模型的编程。pthread线程通过libpthread线程函数库来实现。所以在编译多线程程序的时候,需要链接libpthread,比如
源代码: pthread_test.c

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

void* mythread(void * arg)
{
	int i;
	for(i = 0; i < 5; i++)
	{
		printf("pthread\n");
		sleep(1);
	}
	return (void*)0;
}

int main(int argc, char** argv)
{
	pthread_t pth;
	int i, ret;
	ret = pthread_create(&pth, NULL, mythread, NULL);
	if(ret != 0)
	{
		printf("create pthread error\n");
		exit(1);
	}
	for(i = 0; i < 5; i++)
	{
		printf("main process\n");
		sleep(1);
	}
	return 0;
}

上面的代码,第二个参数我们设置为空指针,这样将生成默认属性的线程。函数mythread如果不需要参数,那么最后一个参数也可以设置为空指针。

运行结果:
在这里插入图片描述
两个线程的输出是交替的。

错误打印
在这里插入图片描述
strerror返回的是strerror申请的空间,多次调用strerror,将改变这个值,出现意外的结果,strerror_r让调用者自己申请一个buf来存放。

线程的退出

一种是子线程程序运行完自动退出或者直接return,一种是调用pthread_exit(),其函数原型为:
在这里插入图片描述
函数的参数时线程退出时的返回码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给主线程。

调用线程退出函数,注意和exit函数的区别,任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。 需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

源代码:pthread_test2.c

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

void* mythread(void * arg)
{
	int i;
	for(i = 0; i < 5; i++)
	{
		printf("pthread\n");
		sleep(1);
	}
	pthread_exit((void *)i);
}

int main(int argc, char** argv)
{
	pthread_t pth;
	int i, ret;
	void* result;
	ret = pthread_create(&pth, NULL, mythread, NULL);
	if(ret != 0)
	{
		printf("create pthread error\n");
		exit(1);
	}
	for(i = 0; i < 5; i++)
	{
		printf("main process\n");
		sleep(1);
	}
	pthread_join(pth, &result);
	printf("pthread exit code = [%d]\n", (int)result);
	return 0;
}

运行结果:
在这里插入图片描述
线程回收

linux线程执行有两种状态joinable状态和unjoinable状态。

1、如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符。只有当你调用了pthread_join之后这些资源才会被释放。

函数pthread_join(),这个函数是主线程用于等待子线程结束,其函数原型是:
在这里插入图片描述
第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用该函数将一直等待到被等待的线程结束为止,这函数返回时,被等待线程的资源被回收。

2、若是unjoinable状态的线程,这些资源在线程函数退出时或 pthread_exit 时自动会被释放。

unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()) ,将状态改为unjoinable状态,确保资源的释放。其实简单的说就是在线程函数头加上 pthread_detach(pthread_self()) 的话,线程状态改变,在函数尾部直接pthread_exit线程就会自动退出。
在这里插入图片描述
pthread_t tid : 分离线程tid 返回值:成功返回0,失败返回错误号。

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

void *my_thread(void *arg)
{
	int i = 3;
	while (i--)
	{
		printf("thread count %d\n", n);
		sleep(1);
	}
	return (void *)1;
}

int main(void)
{
	pthread_t tid;
	void *tret;
	int err;
	pthread_create(&tid, NULL, my_thread, NULL);
	/*第一次运行时注释掉下面这行,第二次再打开,分析两次结果*/
	pthread_detach(tid);
	err = pthread_join(tid, &tret);
	if (err != 0)
		printf("thread %s\n", strerror_r(err));
	else
		printf("thread exit code %d\n", (int)tret);
		sleep(1);
	return 0;
}

pthread_cancel

在进程内某个线程可以取消另一个线程。
在这里插入图片描述
被取消的线程,退出值,定义在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:
在这里插入图片描述
源码:pthread_cancel.c

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

void *th_func(void *arg)
{
	while(1)
	{
		printf("thread run\n");
		sleep(1);
	}
}

int main(void)
{
	pthread_t tid;
	void *ret;
	pthread_create(&tid, NULL, th_func, NULL);
	sleep(3);
	pthread_cancel(tid);
	pthread_join(tid, &ret);
	printf("thread exit code %d\n", (int)ret);
	return 0;
}

同一进程的线程间,pthread_cancel向另一线程发终止信号。系统并不会马上关闭被取消线程,只有在被取消线程下次系统调用时,才会真正结束线程。或调用pthread_testcancel,让内核去检测是否需要取消当前线程。

pthread_equal

比较两个线程是否相等
在这里插入图片描述


线程属性

本节作为指引性介绍,linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
在这里插入图片描述
注:目前线程属性在内核中不是直接这么定义的,抽象太深不宜拿出讲,为方便大家理解,使用早期的线程属性定义,两者之间定义的主要元素差别不大。

线程属性初始化

先初始化线程属性,再pthread_create创建线程。
在这里插入图片描述

线程的分离状态

非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。== 分离状态==:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
在这里插入图片描述
pthread_attr_t *attr : 被已初始化的线程属性。
int *detachstate : 可选为 PTHREAD_CREATE_DETACHED (分离线程)和 PTHREAD _CREATE_JOINABLE (非分离线
程)。

线程设置分离态有两种方式:第一种是在创建函数前直接在结构体里设置分离态,第二种是创建线程后用函数设置成分离态。我们一般使用第一种更好。因为万一创建线程执行代码很短,设置分离态的函数还没执行完,线程函数可能就已经执行完了。

线程的栈大小(stack size)

当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。 函数pthread_attr_getstacksize和pthread_attr_setstacksize提供设置。
在这里插入图片描述
attr 指向一个线程属性的指针。 stacksize 返回线程的堆栈大小。 返回值:若是成功返回0,否则返回错误的编号。

除上述对栈设置的函数外,还有以下两个函数可以获取和设置线程栈属性,当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。传给pthread_attr_setstack函数的地址是缓冲区的低地址(不一定是栈的开始地址,栈可能从高地址往低地址增长)。
在这里插入图片描述
attr 指向一个线程属性的指针 stackaddr 返回获取的栈地址 stacksize 返回获取的栈大小 返回值:若是成功返回0,否则返回错误的编号

猜你喜欢

转载自blog.csdn.net/Gsunshine24/article/details/89063509