目录
1. 线程概念的铺设
2. Linux线程概念
2.1 什么是线程
2.2 线程的优点
2.3 线程的缺点
2.4 线程异常
2.5 线程用途
3. Linux进程VS线程
4. Linux线程控制
4.1 POSIX线程库
4.2 创建线程
4.3 进程ID和线程ID
4.4 线程终止
4.5 线程等待
4.6 分离线程
Linux
在我们之前的学习中都是一个进程对应一个执行流,也就是说进程是OS分配资源的基本单位,也是CPU调度的基本单位;
今天,我们便要学习一个进程对应一个执行流,或者是一个进程对应多个执行流的情况,这里的每个执行流便可称为一个线程;
也就是说一个进程内是可能存在多个线程的,进程与线程数之比=1:n;内核中有可能存在着大量的线程,OS便要对这些线程进行管理。“先描述再组织”是OS管理对象的一个准则,线程是通过线程控制块(TCB)描述的,这是常规的一些操作系统的做法,比如Windows;
在Linux中是没有专门为线程设计TCB的,而是用进程PCB来模拟线程。
下面用一张图来大概说明下线程与进程:
对于线程来说只创建task_struct,将当前进程的资源(代码+数据)划分为若干份让每个task_struct用,每个task_struct就是一个需要被调度的执行流;对于CPU来说,此时看到的task_struct是小于原先的task_struct的,这里的task_struct也成为轻量级进程;多个线程是共享同一进程的地址空间的;
这样做不用维护复杂的进程和线程的关系,不用单独为线程设计任何算法,直接使用进程的一套相关的方法,OS只需要聚焦在线程间的资源分配上就好了;
进程的今昔对比:
Linux线程与接口关系的认识:
Linux中的线程是用进程模拟的,Linux中没有提供直接操作线程的接口,只是提供了在同一地址空间内创建task_struct的方法,分配资源给指定的task_struct的接口,这种方法对用户特别不友好,系统级别的工程师在用户层,对Linux轻量级进程接口进行了封装,给我们打包成了库,让用户直接使用库函数,这个库称为 原生线程库 是用户层的;
在这里要注意的一点是:
对于计算密集型应用,并不是说线程越多是越好的,一般线程的个数与CPU的核数相等即可,如果线程太多会导致被过度调度切换(有成本的),假如只有一个CPU划分了10个线程,这样的话还不如让一个进程直接在CPU上运行;
对于IO密集型应用,IO是允许多一些线程,如果一个进程需要等待磁盘资源、IO资源,那么便可以将该进程分为多个线程,让等待这些资源的时间重叠,从而缩短整个等待时间。但也不是说越多越好的,因为磁盘只有一个,划分为再多的线程也只有排队等待同一个磁盘资源,无济于事;
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计
算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指
的是增加了额外的同步和调度开销,而可用的资源不变。
2. 健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3. 缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响。
编写与调试一个多线程程序比单线程程序困难得多
1. 进程和线程
在这最重要的是:栈和上下文;
栈保存临时数据,上下文用于CPU的调度切换;
2. 进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
功能:创建一个新的线程
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)
(void*), void *arg);
参数
thread:输出型参数,返回线程ID;
attr:设置线程的属性,attr为NULL表示使用默认属性;
start_routine:是个函数地址,线程启动后要执行的函数;
arg:传给线程启动函数的参数;
返回值:成功返回0;失败返回错误码
#include
#include
#include
#include
#include
void *thread_run(void *arg)
{
int i;
for(;;)
{
printf("I am %s,pid:%d\n",(char*)arg,getpid());
sleep(1);
}
}
int main()
{
//用于接收新创建的线程的id
pthread_t tid;
//用于接收创建线程函数的返回值
int ret;
//创建线程
if((ret=pthread_create(&tid,NULL,thread_run,(void*)"thread1"))!=0)
{
printf("pthread_create:%s\n",strerror(ret));
return 1;
}
//主执行流
int i;
for(;;)
{
printf("I am main thread!pid:%d\n",getpid());
sleep(1);
}
return 0;
}
struct task_struct {
...
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader;
...
struct list_head thread_group;
...
};
查看线程ID:
ps -aL
强调一点:
线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。
在程序中,我们也可以使用函数的方式获得该线程自身的ID:
#include
pthread_t pthread_self(void);
该函数获得的线程ID和刚才查出的线程ID是不同的,我们查到的线程ID是 pthread 线程库的线程ID,使用命令的方式查看的是Linux内核中的LWP,pthread库的线程ID是一个虚拟地址,如下图中的pthread_t id所示;
功能:线程终止
原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
功能:取消一个执行中的线程
原型:
int pthread_cancel(pthread_t thread);
参数:
thread:线程ID
返回值:成功返回0;失败返回错误码
线程等待的原因:
pthread_join函数:
功能:等待线程结束
原型:
int pthread_join(pthread_t thread, void **value_ptr);
参数:
thread:线程ID;
value_ptr:输出型参数,用来获取新线程推出时候的函数的返回值;
返回值:成功返回0;失败返回错误码
#include
#include
#include
#include
void *thread1( void *arg )
{
printf("thread 1 returning ... \n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
void *thread2( void *arg )
{
printf("thread 2 exiting ...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
}
void *thread3( void *arg )
{
while(1)
{
printf("thread 3 is running ...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
void *ret;
//thread1 return
//创建线程1
pthread_create(&tid, NULL, thread1, NULL);
//等待线程1
pthread_join(tid,&ret);
printf("thread return, thread id:%X, return code:%d\n",tid,*(int*)ret);
free(ret);
//thread2 exit
//创建线程2
pthread_create(&tid, NULL, thread2, NULL);
//等待线程2
pthread_join(tid,&ret);
printf("thread return, thread id:%X, return code:%d\n",tid,*(int*)ret);
free(ret);
//thread3 cancel by other
//创建线程3
pthread_create(&tid, NULL, thread3, NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid,&ret);
if(ret==PTHREAD_CANCELED)
printf("thread return, thread id:%X, return code:PTHREAD_CANELED\n");
else
printf("thread return, thread id:%X, return code:NULL\n",tid);
return 0;
}
线程对于整个程序来说,根本上也是用函数的形式呈现的;
函数退出时有三种情况:
对于上述3种情况,我们只 join 前两种,因为线程代码异常的话,OS会发信号给进程,整个进程就瘫痪了,无需考虑此情况;
int pthread_detach(pthread_t thread);
#include
#include
#include
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, (void*)"thread1")!=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;
}
如果本篇博客对您有所收获的话,还请点赞、收藏加关注