目录
前言
POSIX线程库
线程控制
创建线程
线程终止
pthread_exit()函数
pthread_cancel()函数(会在下面线程等待部分详解)
线程等待
pthread_join()函数
获取线程退出码
分离线程
线程取消(pthread_cancel()函数)
线程ID及进程地址空间分布
前言
好久不见!各位支持我的IT人,前段时间由于准备蓝桥杯需要学习一段时间算法就停止更新Linux的内容;由于自己也是算法初学者,想着蓝桥杯突击一个多月算法就行了没必要更新算法博客,后面还会专门学习算法到那时候在更新算法的内容。停更前对Linux中的线程做了一个简单的介绍,在接下来的几篇文章我们会深入探讨Linux的线程这方面的问题。线程这方面学习完后,Linux系统编程就结束了,接下来便是网络编程和数据库;在网络编程学习中会编写我的第一个项目,到时候希望大家多多支持!!!
POSIX线程库
线程是操作系统中的一个概念,在Linux这个特定的操作系统中是没有线程这个概念的;是用轻量级进程(LWP)来实现所谓操作系统中的线程的;因此Linux操作系统只会提供轻量级进程的的系统调用,不会提供线程创建的接口。基于此原因我们要在操作系统和用户之间包含一个软件层对用户层向上提供线程的控制接口,对操作系统提供轻量级进程的控制接口。这并不繁琐,可以让操作系统和用户层实现解耦,反而这是Linux操作系统的一大亮点。这个软件层就是POSIX线程库,这是一个原生库,对于每个Linux系统都会包含这个库;因此我们在用户层创建线程时需要引入这个库。
线程控制
创建线程
函数原型
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:传给线程启动函数的参数
注意:第三个参数是一个返回值为void*参数为void*的函数指针,第四个void*参数作为第三个函数指针的参数。
返回值:成功返回0;失败返回错误码
简单代码示例
void* ThereadTountine(void* args)
{
const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换
while(true)
{
sleep(1);
cout<<p<<endl;
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,ThereadTountine,(void*)"new thread-1");
while(true)
{
sleep(1);
cout<<"main thread"<<endl;
}
return 0;
}
这里只是创建了一个新线程,我们可以使用循环配合这个线程创建函数创建更多的线程。
注意:这里的第四个参数不仅仅可以像上面一样传入一个常量字符串,甚至是可以传入一个自定义对象。
线程终止
如果我们只终止某个线程而不终止整个进程,我们可以有三种方法
- 1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 2. 线程可以调用pthread_ exit终止自己。(新线程调用的函数不可以使用exit函数,因为exit使用来终止整个进程的,而线程只是整个进程中的一个执行流)
- 3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
pthread_exit()函数
功能:终止线程
函数原型:
void pthread_exit(void *value_ptr);
参数:
value_ptr:value_ptr不要指向一个局部变量。
返回值:
无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel()函数(会在下面线程等待部分详解)
功能:
取消一个执行中的线程
函数原型:
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;失败返回错误码
获取线程退出码
线程创建函数的返回值为void * ,而线程等待的第二个参数为void**;这个参数是一个输出型参数,可以获取到函数退出的推出信息。
void* ThereadTountine(void* args)
{
const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换
int cnt=5;
while(cnt--)
{
sleep(1);
cout<<p<<endl;
}
return (void*)"new thread-1 done";
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,ThereadTountine,(void*)"I am new thread-1");
void * ret=nullptr;
int n = pthread_join(tid,&ret);
cout<<(const char *) ret<<endl;
return 0;
}
对于线程出异常我们根本不许用考虑,因为当某个线程出异常的时候,整个进程都会挂掉;等待线程已经没有什么意义了。
分离线程
如果我们线程一直不退出时,主线程会一直阻塞等待这个线程。我们可以将这个线程设置为游离状态。
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
- 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
因此我们可以根据需求动态决策,线程的状态。
函数原型
如果想要分离一个线程,可以在主线程中调用函数将新线程分离,也可以在新线程中将自已与主线程分离。
int pthread_detach(pthread_t thread);
//分离自己
pthread_detach(pthread_self());
注意:joinable和分离是冲突的,一个线程不可以既是joinable又是分离的。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void *ThereadTountine(void *args)
{
// 新线程和主线程分离
//pthread_detach(pthread_self());
const char *p = (char *)args; // 这里也可以使用c++11中的安全类型转换
int cnt = 5;
while (cnt--)
{
sleep(1);
cout << p << endl;
}
return (void *)"new thread-1 done";
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThereadTountine, (void *)"I am new thread-1,running ~ ~ ");
//主线程和新线程分离
pthread_detach(tid);
sleep(1);//保证新线程已经执行了一秒
int n = pthread_join(tid, nullptr);
cout << n << endl;
return 0;
}
线程取消(pthread_cancel()函数)
void *ThereadTountine(void *args)
{
const char *p = (char *)args; // 这里也可以使用c++11中的安全类型转换
int cnt = 5;
while (cnt--)
{
sleep(1);
cout << p << endl;
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, ThereadTountine, (void *)"I am new thread-1,running ~ ~ ");
sleep(5);//保证新线程已经执行了一秒
//取消新线程
int n = pthread_cancel(tid);
cout<<"cancle n:"<<n<<endl;
void* ret=nullptr;
n = pthread_join(tid, &ret);
cout<<"join n:" << n << " thread return:"<<(int64_t) ret<<endl;
return 0;
}
当线程被取消后其退出码为-1;当线程被分离时可以被取消,但是不可以被等待。因此对于分离的线程在运行时我们可以对其取消操作。
线程ID及进程地址空间分布
在新线程中可以使用pthread_self(),来获取自身ID;
这个ID看起来非常的大,我们可以尝试转化为16进制进行打印;
转化为十六进制后,这个线程ID和虚拟地址非常的类似。
void* ThereadTountine(void* args)
{
const char * p = (char *) args;//这里也可以使用c++11中的安全类型转换
while(true)
{
sleep(1);
cout<<p<<endl;
printf("new thread ID : %x\n",pthread_self());//获取自身线程ID
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,nullptr,ThereadTountine,(void*)"I am new thread-1");
while(true)
{
sleep(1);
cout<<"I am main thread"<<endl;
printf("main ID : %x\n",pthread_self());
}
return 0;
}
当我们在用户层创建了n个线程时,通过线程库操作系统会创建n个轻量级进程,对于这些轻量级进程来说可以复用进程那一套管理方案来解决。我们所谓的“线程”是不过是用户级线程,对于这些线程来说我们也要进行管理——“先描述,在组织”,因此我们要有一个TCB(Thread Control Block,线程控制块),这个TCB并不存在于操作系统中,存在于库中。TCB中含有LWP ID 等关于这个线程的各种属性。但是对于每个单独的线程来说,还要包含存储独立上下文的存储结构和独立的栈空间。库是共享的在共享区中只包含一次就可以,然而线程可以是很多的,我们可以将每个线程的TCB和进程上下文的存储空间和独立的栈描述起来,然后使用一个数组将其组织起来;将每个线程的起始地址作为TID向上返回给用户。
两个小问题
线程可以fork()创建子进程吗?
可以,在前面进程的学习中我们是只含有一个执行流的进程,在这个进程中创建子进程;现在我们这个进程中的进程中含有多个执行流,只不过在这众多执行流中fork子进程。
线程可以进行程序替换吗?
可以,但不推荐!因为线程公用主线程的公共资源,进行程序替换可能会导致,整个进程被替换;应该在众多线程中的一个线程中fork一个进程后进行程序替换。
今天对Linux下线程控制的各种操作的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!!