Linux 多线程概述
1.1 概述
进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。这就造成进程在进行切换等操作时都需要有比较负责的上下文切换等动作,OS并发性也有所下降。
线程的出现: 为了进一步减少处理器的空转时间,提升系统的并发程度,并且支持多处理器和减少上下文切换开销,也就出现了线程。
并发与并行:
- 并行:同一时刻多个进程或线程都在运行。
- 并发:一个时间段内多个进程或线程在运行,但是同一时间点上面只有一个进程或线程在运行。
进程与线程的关系与区别:
-
进程是操作系统中资源分配的基本单位。线程是CPU调度和程序执行的最小单元。
-
一个进程内可以有多个线程,默认一个进程内最少有一个线程,这个线程是主线程。
-
线程优点:开销小,切换快,线程间共享同一进程的地址空间,通信很方便。缺点:不利于资源的管理和保护。
-
进程优点:便于资源的管理和保护,因为进程间是独立的,每个进程有自己的资源,缺点:开销大,速度慢,进程间有数据传递时要用进程间通信,开销多,不方便。
1.2 线程分类
按调度者分为用户级线程和核心级线程
- 用户级线程:主要解决上下文切换问题,调度算法和调度过程全部由用户决定,在运行时不需要特定的内核支持。缺点是无法发挥多处理器的优势
- 核心级线程:允许不同进程中的线程按照同一相对优先调度方法调度,发挥多处理器的并发优势
现在大多数系统都采用用户级线程和核心级线程并存的方法。一个用户级线程可以对应一个或多个核心级线程,也就是“一对一”或“一对多”模型
1.3 进程与线程的操作Linux实现
线程的创建和退出
2.1 线程的创建
线程的创建使用 pthread_create, 创建完即确定了线程函数的入口点,并开始运行相关的线程函数,运行完毕,线程会主动退出。
#include <pthread.h>
int pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
函数 pthread_create 用来创建线程。返回值:成功,则返回 0;失败,则返回对应错误码。
各参数描述如下:
- 参数 thread 是传出参数,保存新线程的标识;
- 参数 attr 是一个结构体指针,结构中的元素分别指定新线程的运行属性,attr 可以用 pthread_attr_init 等
函数设置各成员的值,但通常传入为 NULL 即可; - 参数 start_routine 是一个函数指针,指向新线程的入口点函数,线程入口点函数带有一个 void *的参数由 pthread_create 的第 4 个参数传入;
- 参数 arg 用于传递给第 3 个参数指向的入口点函数的参数,可以为 NULL,表示不传递
- 创建线程后,可以通过ps -elLf命令看到线程
2.2 线程的退出
线程的退出的方式还可以使用 pthread_exit 函数,这是线程主动退出行为
void pthread_exit(void *retval);
函数 pthread_exit 表示线程的退出。其参数可以被其它线程用 pthread_join 函数(下面讲)捕获。
演示
编译时需要带上线程库选项:
gcc -o a a.c -lpthread
#include <stdio.h>
#include <pthread.h>
void *ThreadFunc(void *pArg) //参数的值为 123
{
int i = 0;
for(; i<10; i++)
{
printf("Hi,I'm child thread,arg is:%ld\n", (long)pArg);
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_t thdId;
pthread_create(&thdId, NULL, ThreadFunc, (void *)123 );
int i = 0;
for(; i<10; i++)
{
printf("Hi,I'm main thread,child thread id is:%x\n", thdId);
sleep(1);
}
return 0;
}
线程的等待退出
3.1 等待线程退出
线程从入口点函数自然返回时,函数返回值可以被其它线程用 pthread_join 函数获取
#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return)
该函数是一个阻塞函数,一直等到参数 th 指定的线程返回;与多进程中的 wait 或 waitpid 类似。
thread_return 是一个传出参数,接收线程函数的返回值。
1. 如果线程通过调用 pthread_exit()终止,则pthread_exit()中的参数相当于自然返回值,
照样可以被其它线程用 pthread_join 获取到。
2. thid 传递 0 值时,join 返回 ESRCH 错误。
接一个指针类型的退出状态。
char *pRet=NULL;
pthread_join(thid,(void**)&pRet);
接一个long类型的变量,用这个变量的值表示线程不同的退出状态。
long threadRet=0;
pthread_join(thid,(void**)&threadRet);
该函数还有一个非常重要的作用,由于一个进程中的多个线程共享数据段,因此通常在一个线程退出后,退出线程所占用的资源并不会随线程结束而释放。如果 th 线程类型并不是自动清理资源类型的
则 th 线程退出后,线程本身的资源必须通过其它线程调用 pthrea