一,线程概念
在一程序内,一个执行路线称为线程thread,即线程是一个进程内部的控制序列;
- 一切进程至少都有一个执行线程;
- 线程在进程内部运行,本质是在进程地址空间内运行;
- 在Linux系统中,CPU看到的PCB都要比传统的进程更加轻量化;
- 透过进程虚拟地址空间,可看到进程的大部分资源;将进程资源合理分配给每个执行流,就形成线程执行流;
线程优点
- 创建新线程的代价要比创建新进程小的多;
- 与进程间的切换相比,线程间的切换需要操作系统所做的工作要少很多;
- 线程占用资源比进程少很多;
- 能充分利用多处理器的可并行数量;
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务;
- 计算密集型应用,为了提高性能,将I/O操作重叠;线程可同时等待不同的I/O操作;
线程缺点
- 性能损失,一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享一个处理器;如计算密集型线程的数量比可用的处理器多,可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变;
- 健壮性降低,编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话即线程间缺乏保护;
- 缺乏访问控制,进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响;
- 编程难度提高,编写与调试一个多线程程序比单线程困难的多;
线程异常
- 单个线程如出现除零,野指针等问题导致线程崩溃,进程也会随之崩溃;
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程;进程终止,该进程内的所有线程也会退出;
线程用途
- 合理的使用多线程,能提高CPU密集型程序的执行效率;
- 合理的使用多线程,能提高I/O密集型程序的用户体验;
二,Linux进程与线程
- 进程是资源分配的基本单位;
- 线程是调度的基本单位;
- 线程共享进程数据,但也拥有自己的一部分数据;
- 线程ID;
- 一组寄存器;
- 栈;
- errno;
- 信号屏蔽字;
- 调度优先级;
进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的;如定义一个函数,在各个线程中都可调用,如定义一个全局变量,在各线程都可访问到,除此之外,各线程还共享以下进程资源和环境:
- 文件描述符;
- 每种信号的处理方式(SIG_IGN、SIG_DFL或自定义信号处理函数);
- 当前工作目录;
- 用户id和组id;
三,Linux线程控制
POSIX线程库
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以”pthread_“开头的;
- 要使用这些函数库,引用头文件<pthread.h>;
- 链接这些线程函数库,使用编译器命令”-lpthread“选项;
创建线程
//创建新线程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);
- thread,返回线程的ID;
- attr,设置线程的属性,如为NULL表示使用默认属性;
- start_routine,函数地址,线程启动后执行的函数;
- arg,传给线程启动函数的参数;
返回值
- 成功返回0,失败返回错误码;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void* rout(void* arg){
for( ; ; ){
printf("I am thread1\n");
sleep(1);
}
}
int main(){
pthread_t tid;
int ret = pthread_create(&tid, NULL, rout, NULL);
if(ret != 0){
fprintf(stderr, "pthread_create: %s\n", strerror(ret));
exit(EXIT_FAILURE);
}
for( ; ; ){
printf("I am main thread\n");
sleep(1);
}
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ldd test
linux-vdso.so.1 => (0x00007ffce0bb2000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9ab6dd8000)
libc.so.6 => /lib64/libc.so.6 (0x00007f9ab6a0a000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9ab6ff4000)
[wz@192 Desktop]$ ./test
I am main thread
I am thread1
I am main thread
I am thread1
I am main thread
I am thread1
线程ID及进程地址空间布局
- pthread_create函数会产生一个线程ID,存放在第一参数所指向的地址;此线程ID与前面所说的线程ID不是一回事;此前所说的线程ID属于进程调度范畴,因线程是轻量级进程,是OS调度的最小单位,所以需一个数值来唯一标识该线程;
- pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元地址即为新创建线程的线程ID,属于NPTL线程库范畴;线程库的后续操作,就是根据该线程ID来操作线程的;
- 线程库NPTL提供了pthread_self函数,可获得线程自身ID;
pthread_t类型是什么,取决于实现;对于Linux目前实现的NPTL,pthread_t类型的线程ID,本质上是一个进程地址空间的一个地址;
线程终止
如需终止某个线程而不是整个进程,有三种方法:
- 从线程函数return,此方法对主线程不适用,从main函数return相当于调用exit;
- 线程可调用pthread_exit终止自己;
- 线程可调用pthread_cancel终止同一进程中的另一个线程;
void pthread_exit(void* value_ptr);
- vaule_ptr,不要指向一个局部变量;
- pthread_exit或return返回的指针指向的内存单元必须是全局或是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时线程函数已经退出;
int pthread_cancel(pthread_t thread);
- 成功返回0,失败返回错误码;
线程等待
为何需要线程等待:
- 已退出的线程,其空间没有被释放,仍然在进程的地址空间内;
- 创建新的线程不会复用刚才退出线程的地址空间;
int pthread_join(pthread_t thread, void** value_ptr);
- value_ptr,指向一个指针,然后在指向线程的返回值;
- 成功返回0,失败返回错误码;
调用该函数的线程将挂起等待,直到id为thread的线程终止;thread的线程以不同的方法终止,通过pthread_join得到的终止状态也是不同的:
- 如thread线程通过return返回,value_ptr所指向的单元存放的时thread线程函数的返回值;
- 如thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数;
- 如thread线程被别的线程调用pthread_cancel异常终止的,value_ptr所指向的单元存放的是常数PTHREAD_CANCELED;
- 如对thread线程的终止状态不感兴趣,可对value_ptr传NULL;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void* thread1(void* arg){
printf("thread1 returning ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
void* thread2(void* arg){
printf("thread1 exiting ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
}
void* thread3(void* arg){
while(1){
printf("thread3 running ...\n");
sleep(1);
}
return NULL;
}
int main(){
pthread_t tid;
void* ret;
//线程1,return
pthread_create(&tid, NULL, thread1, NULL);
pthread_join(tid, &ret);
printf("thread1 return, thread id %x, return code: %d\n", tid, *(int*)ret);
free(ret);
//线程2,exit
pthread_create(&tid, NULL, thread2, NULL);
pthread_join(tid, &ret);
printf("thread2 return, thread id %x, return code: %d\n", tid, *(int*)ret);
free(ret);
//线程3,cancel by other
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid, &ret);
if(ret == PTHREAD_CANCELED)
printf("thread3 return, thread id %x, return code: PTHREAD_CANCELED\n", tid);
else
printf("thread3 return, thread id %x, return code: NULL\n", tid);
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread1 returning ...
thread1 return, thread id 2d3f6700, return code: 1
thread1 exiting ...
thread2 return, thread id 2d3f6700, return code: 2
thread3 running ...
thread3 running ...
thread3 running ...
thread3 return, thread id 2d3f6700, return code: PTHREAD_CANCELED
线程分离
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄露;
- 如不关心线程的返回值,join是一种负担,此时可告诉系统,当线程退出时,自动释放线程资源;
- joinable与detach是冲突的,线程不可既是joinable又是detach;
int pthread_detach(pthread_t thread);
//可是线程组内其他线程对目标线程进行分离,也可是线程自己分离
pthread_detach(pthread_self());
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void* thread_run(void* arg){
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
}
int main(){
pthread_t tid;
if(pthread_create(&tid, NULL, thread_run, "thread run ...\n") != 0){
printf("create thread error\n");
return 1;
}
int ret = 0;
sleep(1); //很重要,要让线程先分离,在等待
if(pthread_join(tid, NULL) == 0){
printf("pthread wait success\n");
ret = 0;
} else{
printf("pthread wait failed\n");
ret = 1;
}
return ret;
}
[wz@192 Desktop]$ gcc -o test test.c -lpthread
[wz@192 Desktop]$ ./test
thread run ...
pthread wait failed