Linux之线程:控制与分离

之前我一直都提到的是进程,现在多了一个线程的概念,从字面意思来看,线程应该比进程小。嘿嘿。

其实操作系统刚开始的时候,提出进程概念后,操作系统一直都是以进程作为独立运行的基本单位,然后有人感觉了,这不对呀,进程之间的中断转换太浪费了,并且用户态到核心态的切换也有点麻烦,所以在80年代中期,人们又提出了毕竟更小的独立运行的基本单位咯--线程,用来提高系统内存程序的并发执行程度。所以线程就这么出现了。

那,什么是线程呢?

其实简单来说,线程就是进程中的执行分流,从操作系统内部来说其实就是一个指令序列,他执行了地址上的跳转,但是却不影响其他流程的执行。进程它所针对的是独占我们所有的资源,但是线程确实在单个进程之下进行资源的共享,但是共享虽然共享,但是人嘛,不管怎么过度曝光,都有着自己的隐私,所以我们看一下线程所需要拥有的属性:

由于同一进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果

定义一 个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

1. 文件描述符表

2. 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)

3. 当前工作目录

4. 用户id和组id

但有些资源是每个线程各有一份的:

1. 线程id

2. 上下文,包括各种寄存器的值、程序计数器和栈指针

3. 栈空间

4. errno变量

5. 信号屏蔽字

6. 调度优先级

以上是关于线程的概念,以后我会提到用户态线程与核心态线程,这里暂且不提,我们来看一下如何在Linux下使用线程。

下面来讲解一下线程控制:

首先要注意一点的是,之前提到的进程都是system V版本的系统,所以他的库调用都是

#include<sys/XXX>

但是关于线程,我们遵循的是POSIX版本的系统,一定要注意这一点。

然后看一下线程的创建函数:注意在编译包含使用线程的程序是,需要在gcc命令后面加上-lpthread

#include<pthread.h>
pthread_create(pthread_t *thread,const pthread_attr_t *attr,
                void*(*start_routine)(void *),void* arg);

返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误

号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都

有 一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值

返回错误码更加清晰。

在一个线程中调用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_join。

pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid(2)可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self(3)可以获得当前线程的id。

attr参数表示线程属性.

下面看一下程序代码:

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

pthread_t tid;
void* thread_run(void *_val)
{
	printf("%s,pid is :%d,tid is :%u\n",(char*)_val,(int )getpid(),(unsigned long long)pthread_self());
}

int main()
{
	int ret = pthread_create(&tid,NULL,thread_run,"other thread run");
	if(ret != 0)
	{
		printf("creater error:%s\n",strerror(ret));
		exit(ret);
	}

	printf("main thread run:pid is :%d,tid is :%u\n",(int )getpid(),(unsigned long long)pthread_self());
	sleep(1);
	return 0;
}

如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,

运行结果:

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

3. 线程可以调用pthread_exit终止自己。

用pthread_cancel终止一个线程分同步和异步两种情况,比较复杂,不打算详细介绍,同学们先自己研究。

然后我们看一下它的其他几个要求函数:

#include<pthread.h>
void pthread_exit(void *retval);
int pthread_join(pthread_t thread,void **retval);

1. 如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返

回值。

2. 如果thread线程被别的线程调用pthread_cancel异常终掉,value_ptr所指向的单元里存放

的是常数PTHREAD_CANCELED。

3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

操作代码:

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

void* thread1(void *_val)
{
	printf("thread 1 returning ...\n");
	return (void*)1;
}
void* thread2(void *_val)
{
	printf("thread 2 exiting ...\n");
	pthread_exit((void*)2);
}
void* thread3(void *_val)
{
	while(1)
	{
		printf("pthread 3is running,wait for be cancal...\n");
		sleep(1);
	}
	return NULL;
}

int main()
{
	pthread_t tid;
	void *tret;
	//thread 1 return 
	pthread_create (&tid,NULL,thread1,NULL);
	pthread_join(tid,&tret);
	printf("thread return ,thread id is :%u,return code is : %d\n",(unsigned long long )tid,tret);

	//thread 2 return 
	void *tret2;
	pthread_create (&tid,NULL,thread2,NULL);
	pthread_join(tid,&tret2);
	printf("thread return ,thread id is :%u,exit code is : %d\n",(unsigned long long )tid,tret2);

	//thread 1 return 
	void *tret3;
	pthread_create (&tid,NULL,thread3,NULL);
	sleep(3);
	pthread_cancel(tid);
	pthread_join(tid,&tret3);
	printf("thread return ,thread id is :%u,cancel code is : %d\n",(unsigned long long )tid,tret3);
	return 0;
}

运行结果:

返回值有问题,我也不知道为什么。。。

其中需要注意点,当创建一个线程,并且在join等待一个线程时,需要当前的逻辑流执行完以后才能执行下面的代码,也就是为什么3个线程的ID是相同的了。

有关分离线程

在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join;要么通过调用pthread_detach函数被分离。

如果一个可结合线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码

pthread_detach(pthread_self())

或者父线程调用

pthread_detach(thread_id)(非阻塞,可立即返回)

这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。

代码如下:

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

void *thread_run(void *_val)
{
	pthread_detach(pthread_self());
	printf("%s\n",(char *)_val);
	return NULL;
}

int main()
{
	pthread_t tid;
	int tret = pthread_create(&tid,NULL,thread_run,"thread1 run...");
	if(tret != 0)
	{
		printf("create thread error,info is :%s\n",strerror(tret));
		return tret;
	}
	int ret = 0;
	sleep(1);
	if(0 == pthread_join(tid,NULL))
	{
		printf("wait 1");
		ret = 0;
	}
	else{
		printf("wait failed");
		ret = 1;
	}
	return ret;
}

运行结果:

好了,总结一下:

其实关于线程,我们在理解他的出现由来和基本概念之后我们知道为什么需要他的存在,例如我们加载一个网页,图片,视频,还有文字是分别加载的,如果用单进程进行加载的话他们是交错出现,但是如果使用多线程的话他们可以同时进行加载,并且也比进程快,所以线程的出现解决了模块问题的并发或者单列处理。

然后就是关于Linux系统下的线程编程了,其实只要理解线程的创建,等待,分离,终止,就可以算作是我们能够操纵进程了,记住他的几个相关操作函数,就OK了,其实线程就是进程的肢体而已。

以上。

时间: 2024-11-29 00:29:26

Linux之线程:控制与分离的相关文章

Linux/UNIX线程控制

线程控制 线程属性 调用pthread_create函数的例子中,传入的参数都是空指针,而不是指向pthread_attr_t结果的指针.可以用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来.可以使用pthread_attr_init函数初始化pthreaad_attr_t结构.调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值.如果要修改其中个别属性的值,需要调用其他的函数.pt

线程控制与分离

线程: 在一个进程的地址空间中执行多个线程 ---- 强调共享  线程是进程中的一个实体. 线程私有: 线程id 上下文信息 (包括各种寄存器的值.程序计数器和栈指针) (私有)栈空间    errno变量    信号屏蔽字    调度优先级 此时:POSIX标准   编译时  加上 -lpthread 线程与进程的区别: 1.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位: 线程是进程的一个实体. 2.进程 强调 独占,线程 强调 共享

Linux下线程的控制与分离

一.线程的概念: 线程是运行在进程地址空间的,是进程的执行分流.它是执行进程内部的一个代码片段为了让进程更好的完成.也可以说,进程是承担系统资源的实体,而线程进程运行调度的基本单位. 由于同一进程的多个线程共享同一地址空间,因此Text Segment.Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境: 1. 文件描述符表 2. 每种信号的处理方式(SIG_IGN.SIG_DFL或者

linux线程控制&amp;线程分离

线程概念 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元. 线程是程序中一个单一的顺序控制流程.进程内一个相对独立的.可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位.在单个程序中同时运行多个线程完成不同的工作,称为多线程. 线程资源 由于一个程序中的多个线程共享同一地址空间,因此代码段,数据段内容是共享的.除此之外,以下内容也是共享的: 1. 文件描述符表2. 每种信号的处理方式(SIG_IGN.SIG_DFL

线程的控制和分离

线程的概念:线程是运行在进程内的一个基本执行流,和进程共享地址空间及资源(类似于父子进程共享地址空间),但每个也有自己的私有资源. 进程强调独占性 每个进程都有它独立的地址空间,包括Text Segment.Data Segment等 线程强调共享性 线程的共享资源: 1.进程代码段 2.进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯) 3.进程打开的文件描述符 4.信号的处理器 5.进程的当前目录和进程用户ID与进程组ID 线程的私有资源:(线程实现并发性)       1

Linux之线程、线程控制、线程属性

一.整体大纲 二.线程相关 1. 什么是线程    LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下) 进程:独立地址空间,拥有PCB 线程:也有PCB,但没有独立的地址空间(共享) 区别:在于是否共享地址空间. 独居(进程):合租(线程). Linux下: 线程:最小的执行单位 进程:最小分配资源单位,可看成是只有一个线程的进程. 2. Linux内核线程实现原理     (1)线程实现原理 类Unix系统中,早期是没有“线程”概念的,80年代才

linux c 笔记 线程控制(二)

linux 下有两种方式可以使线程终止,一种是通过调用return 从线程函数返回,第二种是通过调用函数 #includevoidpthread_exit(void *retavl); 需要注意的地方:一是,主线程中如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时所有的其他线程也将终止.另一种是,如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,其他线程也不会结束,直到所有的线程都结束时,进程才结束. 线程的分离状态决定一个线程以什么样的方

Linux/UNIX线程(1)

线程(1) 本文将介绍怎样使用多个控制线程在单个进程环境中运行多个任务. 一个进程中的全部线程都能够訪问该进程的组成部件(如文件描写叙述符和内存). 线程包含了表示进程内运行环境必须的信息,当中包含进程中标识线程的线程ID.一组寄存器值.栈.调度优先级和策略.信号屏蔽字.errno变量以及线程私有数据.进程的全部信息对该进程的全部线程都是共享的,包含可运行的程序文本.程序的全局内存和堆内存.栈以及文件描写叙述符. 线程标识 进程ID在整个系统中是唯一的.每一个线程ID.线程ID仅仅在它所属的进程

.NET线程控制快速学习01

最近,由于基础框架的整体升级,因此需要更新所有相关项目的DLL文件.这个过程存在不小的风险,因此也对发布后的生产服务器进行了密切的监控,结果还是出现了个别应用出现异常的情况,很快的占用了大量的服务器内存和CPU等资源.通过研究dump,初步发现是由于配置服务器出现单点故障,然后应用通过多线程调用相关SOA服务时出现异常,引发了ThreadAbortException异常,而且由于原有异常处理代码不够严谨,而且与异步发送报警邮件紧密结合在一起,造成线程数量的几何级增加,最终使得整个服务器不可用.这