文章目录
- 线程理解
- 线程的优点与缺点
- 进程的多个线程共享
- 线程控制
- 线程创建
- 线程终止
- 线程等待
- 线程分离
- 总结
线程理解
谈及线程,就不得不谈起进程与线程的关系了。学习完前面有关进程的知识,之前我们对进程的定义是:内核数据结构+代码和数据。但是今天学习完线程的知识后,再这样定义就是不对的了,因为线程也可以这样定义,这样说也反映了线程和进程极其相似,都有内核数据结构+代码和数据。至于线程到底是个什么东西,接下来我们通过图解来展示。
在CPU执行一个进程时,不再是直接看到整个进程的PCB了,而是看到每个线程的PCB,同时推进多个线程的运行。因此,在一个程序中一个执行线路被称为线程(thread),线程就是执行流,是基本的调度单位。那么此时的进程就称为:承担分配系统资源的基本实体。
线程的优点与缺点
优点 :
1.创建一个新线程的代价要比创建一个新进程小得多
2.与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3.线程占用的资源要比进程少很多
4.能充分利用多处理器的可并行数量
5.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
6.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
缺点:
1.性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集
型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额
外的同步和调度开销,而可用的资源不变。
2.健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享
了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
3.缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4.编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
进程的多个线程共享
同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义
一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表
每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
当前工作目录
用户id和组id
线程共享进程数据,但也拥有自己的一部分数据:
线程ID
一组寄存器
栈
errno
信号屏蔽字
调度优先级
进程和线程的关系如下图:
线程控制
由于Linux没有原生的线程概念以及对应的用户级函数接口,因此衍生出了一个关于线程的库:pthread库,其中绝大部分的函数都是由pthread_开头,在程序中要包含头文件<pthread.h>,并在编译时要加上链接选项 -lpthread。以下所有有关线程的函数调用都要满足以上条件。
生成可执行文件mythread
线程创建
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码
#include<iostream>
#include<cstdio>
#include<pthread.h>
#include<unistd.h>
using namespace std;
void* startRoutine(void* arg)//启动函数
{
const char* name=static_cast<const char*>(arg);
while(true)
{
printf("%s 正在运行……\n", name);
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
int n= pthread_create(&tid,nullptr,startRoutine,(void*)"thread1");
while(true)
{
cout <<"main线程正在运行……"<<endl;
sleep(1);
}
return 0;
}
关于上面参数中的thread参数,这是一个输出型参数,调用后该参数就是该线程在虚拟内存中的地址空间,pthread_t类型的线程ID,本
质就是一个进程地址空间上的一个地址。这里其实还有另一个函数可以调用,同样可以显示线程的地址空间:pthread_self
参数:无
返回值:线程ID
功能:得到调用该函数线程的ID
根据这个函数,上面的代码更改一下也是可以完成相同的工作,并附加对应的线程ID。
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void printTid(const char *name, const pthread_t &tid)
{
printf("%s 正在运行, thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *arg)
{
const char *name = static_cast<const char *>(arg);
while (true)
{
printTid(name, pthread_self());
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1");
while (true)
{
printTid("main thread",pthread_self());
sleep(1);
}
return 0;
}
线程终止
线程的终止一般是调用了某些函数和代码编写的错误才会导致。线程异常终止会导致整个进程的终止,这里我们先考虑比较正常的终止情况:调用pthread_exit、pthread_cancel或使用return直接退出(注意,return不适合在主线程中使用,会导致整个进程的结束)。
功能:结束调用此函数的线程
参数:
retval:用户可以传对应的数据来表明退出时的状态
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
🔺需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程
函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,就会导致线程自己的栈数据被销毁。
功能:结束掉ID为thread的线程。
参数:
thread:线程ID
返回值:成功返回0;失败返回错误码
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void printTid(const char *name, const pthread_t &tid)
{
printf("%s 正在运行, thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *arg)
{
const char *name = static_cast<const char *>(arg);
int cnt=5;
while (true)
{
printTid(name, pthread_self());
if(!cnt--)//五秒后退出线程
{
printf("线程: 0x%x退出\n",pthread_self());
pthread_exit(nullptr);
}
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1");
while (true)
{
printTid("main thread",pthread_self());
sleep(1);
}
return 0;
}
线程等待
和进程等待类似,线程也要等待被回收资源,以免造成内存泄漏的问题。
功能:调用该函数的线程将挂起等待,直到id为thread的线程终止
参数:
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数
PTHREAD_ CANCELED。
如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参
数。
如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
返回值:成功返回0;失败返回错误码
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void printTid(const char *name, const pthread_t &tid)
{
printf("%s 正在运行, thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *arg)
{
const char *name = static_cast<const char *>(arg);
int cnt = 5;
while (true)
{
printTid(name, pthread_self());
if (!cnt--)
{
printf("线程: 0x%x退出\n", pthread_self());
pthread_exit((void *)666);
}
sleep(1);
}
return nullptr;
}
int main()
{
void *retval = nullptr;
pthread_t tid;
int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1");
pthread_join(tid, &retval); // 回收线程资源,也会导致主线程在此处挂起
printf("有线程退出, retval: %d\n",retval);
while (true)
{
printTid("main thread", pthread_self());
sleep(1);
}
return 0;
}
线程分离
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。如果不关心线程的返回值,join是一种负担,这个时候,我们可以使用pthread_detach函数,当线程退出时,自动释放线程资源。但是值得注意的是,线程分离之后是不能够再join的,这两者冲突。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void printTid(const char *name, const pthread_t &tid)
{
printf("%s 正在运行, thread id: 0x%x\n", name, tid);
}
void *startRoutine(void *arg)
{
pthread_detach(pthread_self());//分离线程
const char *name = static_cast<const char *>(arg);
int cnt = 5;
while (true)
{
printTid(name, pthread_self());
if (!cnt--)
{
printf("线程: 0x%x退出\n", pthread_self());
pthread_exit((void *)666);
}
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, startRoutine, (void *)"thread1");
sleep(1);
if(pthread_join(tid,nullptr)==0)
{
printf("pthread wait success\n");
}
else
{
printf("pthread wait failed\n");
}
while (true)
{
printTid("main thread", pthread_self());
sleep(1);
}
return 0;
}
总结
线程控制重点掌握pthread_create、pthread_exit、pthread_cancel、pthread_join、pthread_detach这几个函数就可以,对于主线程与新线程哪个先被调度,这个我们是不清楚的,但是通过pthread_join,一定可以控制新线程先退,主线程后退,这也算是线程控制的细节了。