一、线程的基本概念:
线程也被称为轻量进程,线程是运行中的程序的最小调度单位。
线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。
二、linux下线程的特点
线程不拥有系统资源,只能运行必须的一些数据结构;
它与父进程的其它线程共享该进程所拥有的资源。
线程可以创建和撤消,从而实现程序的并发执行。一般,线程具有就绪、阻塞和运行三种基本状态。
在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度
同一进程的多个线程之间共享以下进程资源和环境:
1.文件描述符表
2.每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)
3.当前工作目录
4.用户id和组id同一进程的多个线程之间的私有资源:
- 线程id(是一个正整数,仅在当前进程内有效,用来标识线程)
- 上下文,包括各种寄存器的值、程序计数器和栈指针
- 栈空间
- errno变量
- 信号屏蔽字
调度优先级
线程在当前进程的内部运行(linux下就是线程在当前进程的地址空间里运行,复用PCB)
所有线程只是进程的一个分支,创建代价小
线程时真正意义的调度的基本单位,进程是系统分配资源的基本单位
linux下的线程是轻量级进程。 linux没下有真正意义的线程,线程是由进程模拟的;
三、线程和进程的区别
(1)
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程比进程的粒度更细,线程是进程执行流中的分支,拥有进程一部分资源。是程序的最小调度单位
(2)
一个线程可以创建和撤销另一个线程;
同一个进程中的多个线程之间可以并发执行。
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
(3)地址空间和其它资源:
进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
(4)通信:
进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
(5)调度和切换:线程上下文切换比进程上下文切换要快得多。
(6)在多线程OS中,进程不是一个可执行的实体。
(7)线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
四、线程控制
1.线程创建
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);1
返回值:
成功返回0,错误返回错误号参数
第一个参数thread:线程id(输出型参数)第二个参数attr:线程属性,一般设置为NULL(表示线程属性取缺省值)
第三个参数start_routine:函数指针,指向新线程即将执行的代码
第四个参数arg:这个指针按什么类型解释由调用者自己定义—->NULL
在Linux上,pthread_t类型是一个地址值,属于同一进程的多个线程调用getpid()可以得到相同的进程号,而调用pthread_self()得到的线程号各不相同。
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,从main函数return也相当于调用exit
2.终止线程
(1)如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
3. 线程可以调用pthread_exit终止自己。
(2)终止线程或执行流:
#include <pthread.h>
void pthread_exit(void *retval);1
retval是void *类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join(稍后介绍)获得这个指针。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
(3)取消线程
#include <pthread.h>
int pthread_cancel(pthread_t thread);
线程是允许被取消的,退出结果为-1,线程自己取消自己(不推荐)退出结果为0。
3.线程等待
(1)为什么要等待线程?
main函数执行的线程称为主线程,多线程的执行顺序由线程调度决定
主线程必须回收其他线程,否则就会产生类似僵尸进程的状况(内存泄漏)。
结论:线程必须被等待
(2)获取当前线程的线程tid:pthread_self
#include <pthread.h>
pthread_t pthread_self(void);
仅仅在当前进程内部有效,作为线程的唯一标识符)
(3)线程等待函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
返回值:成功返回0,失败返回错误码。
参数
thread:线程号,即要等待线程的tid
retval:要等待线程的退出码(输出型参数)
调用该函数的线程将挂起等待,直到id为thread的线程终止。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参数。
(4)多线程的进行执行时,只要有一个线程出错,整个进程就会挂掉(操作系统会向其发信号回收资源,其他线程都跟着退出)。线程运行时,线程只能正常的运行完,退出码表明了其运行状态
线程等待只有一种方式:阻塞式等待
4.代码实现
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
void* thread1(void* val1)
{
printf("thread1 is returning\n");
printf("%s:pid is %d,tid is %u\n",(char*)val1,getpid(),pthread_self());
return (void*)0;//线程终止方式1,用return返回
}
void* thread2(void* val2)
{
printf("thread2 exiting\n");
printf("%s:pid is %d,tid is %u\n",(char*)val2,getpid(),pthread_self());
pthread_exit((void*)2);//线程终止方式2,用pthread_exit退出
}
void* thread3(void* val3)
{
printf("%s:pid is %d,tid is %u\n",(char*)val3,getpid(),pthread_self());
while(1)
{
printf("thread3 is running,waiting for be canceled\n");//线程终止方式3,被其他线程c ancel
sleep(1);
}
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_t tid3;
void* ret;
//thread1 return
pthread_create(&tid1,NULL,thread1,"thread1");//线程1创建
pthread_join(tid1,&ret);//wait thread1
printf("thread1 return,return code is %d\n",(int)ret);
//thread2 exit
pthread_create(&tid2,NULL,thread2,"thread2");//线程2创建
pthread_join(tid2,&ret);//wait thread2
printf("thread2 exit,exit code is %d\n",(int)ret);
//thread3 cancel
pthread_create(&tid3,NULL,thread3,"thread3");//线程3创建
sleep(3);
pthread_cancel(tid3);//线程终止方式3,被其他线程用thread_cancel取消
pthread_join(tid3,&ret);//wait thread3
printf("thread3 cancel,cancel code is %d\n",(int)ret);
printf("main thread run:pid is %d,tid is %u\n",getpid(),pthread_self());
return 0;
}
五、线程属性
(1)程有两种属性:可结合性和可分离性。
任何一个时间点上,线程是可结合的(joinable)或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
默认情况下,线程被创建成可结合的。
为了避免存储器泄漏,每个可结合线程都应该要么被显示地回收,即调用pthread_join被其它线程回收;或者通过调用pthread_detach函数被分离,在线程终止时,其储存器资源由系统自动释放。
由于调用pthread_join后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此。改善方法:
- 可以在子线程中加入代码:主动分离
pthread_detach ( pthread_self() )
-或者 父线程调用:被分离
pthread_detach(thread_id)(非阻塞,可立即返回)
上述两个方法都是将该子线程的状态设置为分离的(detached),这样,该线程运行结束后会由系统自动释放所有资源。
线程可以主动分离,也可以被分离。
分离线程的特点:
一个线程设置为分离线程,则主线程不需要对其等待,待其运行完系统会自动回收
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void* thread4(void* val4)
{
printf("%s:thread4 child detach,thread4 tid is %d\n",(char*)val4,pthread_self());//线程4主动分离
pthread_detach(pthread_self());
return (void*)0;//线程终止
}
void* thread5(void* val5)
{
printf("%s:thread5 father detach,thread5 tid is %d\n",(char*)val5,pthread_self());//线程5被分离
return (void*)0;//线程终止
}
int main()
{
pthread_t tid4;
int ret = pthread_create(&tid4,NULL,thread4,"thread4 is running");//线程创建
if(ret != 0)
printf("creat thread4 error:errno is %d,error info is %s\n",ret,strerror(ret));
sleep(1);//wait---线程等待
int tmp = 0;
tmp = pthread_join(tid4,NULL);
if(0 == tmp)
printf("thread4 wait success\n");
else
printf("thread4 wait failure\n");
pthread_t tid5;
ret = pthread_create(&tid5,NULL,thread5,"thread5 is running");//线程创建
if(ret != 0)
printf("creat thread5 error:errno is %d,error info is %s\n",ret,strerror(re t));
sleep(1);
tmp = pthread_detach(tid5);//线程分离
if(0 == tmp)
{
printf("thread5 is detached success\n");
}
else
{
printf("thread5 is detached failure\n");
tmp = pthread_join(tid5,NULL);//线程等待
if(0 == tmp)
printf("thread5 wait success\n");
else
printf("thread5 wait failure\n");
}
return 0;
}