https://blog.csdn.net/skyroben/article/details/72793409 (博客优秀)
1.背景知识
Linux没有真正意义上的线程,它的实现是由进程来模拟,所以属于用户级线程,位于libpthread共享库(所以线程的ID只在库中有效),遵循POSIX标准。
Windows下有一个真正的数据结构TCB来描述线程。
Linux上两个最有名的线程库LinuxThreads和NPTL。
Linux两个线程模型的比较:
Linux下多线程虚拟地址空间的映射类似于用vfork创建多个子进程。
2.进程和线程的区别
进程:程序的一个动态运行实例,承担分配系统资源的实例。(Linux实现进程的主要目的是资源独占)
线程:在进程的内部运行(进程的地址空间)运行的一个分支,也是调度的基本单位(调度按LWP调度)。(Linux实现线程的主要目的是资源共享)
线程所有的资源由进程提供。
单进程:只有一个进程的线程(LWP=PID)。
LWP:轻量级进程。
由于同一进程的多个线程共享同一地址空间,因 此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
1. 文件描述符表
2. 每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
3. 当前工作目录
4. 用户id和组id
但有些资源是每个线程各有一份的:
1.线程ID
2. 上下文信息,包括各种寄存器的值、程序计数器和栈指针
3. 栈空间
4. errno变量
5. 信号屏蔽字
6. 调度优先级
多线程程序的优点(相对进程比较而言):
1. 多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间,创建销毁速度快。
2.是线程间方便的通信机制。由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
3.进程控制
在Linux系统下,与线程相关的函数都定义在pthread.h头文件中。
创建线程函数———pthread_create函数
-
#include <pthread.h>
-
int pthread_create(pthread_t * thread, const pthread_arrt_t* attr,void*(*start_routine)(void *), void* arg);
(1)thread参数是新线程的标识符,为一个整型。
(2)attr参数用于设置新线程的属性。给传递NULL表示设置为默认线程属性。
(3)start_routine和arg参数分别指定新线程将运行的函数和参数。start_routine返回时,这个线程就退出了
(4)返回值:成功返回0,失败返回错误号。
线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,调用pthread_self()可以获得当前线程的id
进程id的类型时pid_t,每个进程的id在整个系统中是唯一的,调用getpid()可以获得当前进程的id,是一个正整数值。
终止线程———pthread_cancel函数和pthread_exit函数
终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适应,从main函数return相当于调用exit。
2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3. 线程可以调用pthread_exit终止自己。
-
#include <pthread.h>
-
int pthread_cancel(pthread_t thread);
(1)thread参数是目标线程的标识符。
(2)该函数成功返回0,失败返回错误码。
-
#include <pthread.h>
-
void pthread_exit(void * retval);
(1)retval是void *类型,其它线程可以调用pthread_join获得这个指针。需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是由malloc分 配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
(2)pthread_exit函数通过retval参数向线程的回收者传递其退出信息。它执行之后不会返回到调用者,且永远不会失败。
线程等待———pthread_join
-
#include <pthread.h>
-
void pthread_join(pthread_t thread,void ** retval);
(1)调用该函数的线程将挂起等待,直到id为thread的线程终止。
(2)thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,
总结如下:
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参数。
(3)成功返回0,失败返回错误码。可能出现的错误码:
4.分离线程
1.在任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。
2.一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源
(例如栈)是不释放的。(默认情况下线程的创建都是可结合的)
3.一个分离的线程是不能被其他线程回收或杀死的,它的存储器 资源在它终止时由系统自动释放。
4. 如果一个可结合线程结束运行但没有被join,会导致部分资源没有被回收,所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源。
调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞。如何解决这种情况呢?
例如,在Web服务器中当主线程为每个新来的连接请求创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的连接请求),这时可以在子线程中加入代码 pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)(非阻塞,可立即返回)这将该子线程的状态设置为分离的(detached),如此一来,该线程运行结束后会自动释放所有资源。
验证代码:
-
#include <stdio.h>
-
#include <error.h>
-
#include <stdlib.h>
-
#include <pthread.h>
-
void* thread_run(void* _val)
-
{
-
pthread_detach(pthread_self()); //注释这句代码join success
-
printf("%s\n", (char*)_val);
-
return NULL;
-
}
-
int main()
-
{
-
pthread_t tid;
-
int tret = pthread_create(&tid, NULL, thread_run, "thread_run~~~~~");
-
//线程创建成功之后,程序的执行流变成两个,一个执行函数thread_run,一个继续向下执行。
-
if (tret == 0)
-
{
-
sleep(1);
-
int ret = pthread_join(tid, NULL);
-
if (ret == 0)
-
{
-
printf("pthread_join success\n");
-
return ret;
-
}
-
else
-
{
-
printf("pthread_join failed info: %s\n", strerror(ret));
-
return ret;
-
}
-
}
-
else
-
{
-
printf("create pthread failed info: %s", strerror(tret));
-
return tret;
-
}
-
}
运行结果:
分析:
可以看到被分离的线程不可以被等待;一个线程初始为可结合的,要么在父线程等待或分离,要么在子线程分离,只能采取上述几种措施的一种。