Linux线程机制揭秘:从内核实现到用户态编程(二)
1.Linux线程控制
1.1. POSIX线程库
与线程有关的函数构成了⼀个完整的系列,绝⼤多数函数的名字都是以“pthread_”打头的 要使⽤这些函数库,要通过引⼊头⽂ <pthread.h> 链接这些线程函数库时要使⽤编译器命令的“-lpthread”选项
1.2. 创建线程
功能:创建⼀个新的线程原型:int pthread_create ( pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine) ( void *), void *arg);参数 :thread:返回线程 IDattr:设置线程的属性, attr 为 NULL 表⽰使⽤默认属性start_routine:是个函数地址,线程启动后要执⾏的函数arg:传给线程启动函数的参数返回值:成功返回 0 ;失败返回错误码
错误检查:
- 传统的⼀些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指⽰错误。
- pthreads函数出错时不会设置全局变量errno(⽽⼤部分其他POSIX函数会这样做)。⽽是将错误代码通过返回值返回。
- pthreads同样也提供了线程内的errno变量,以⽀持其它使⽤errno的代码。对于pthreads函数的错误,建议通过返回值业判定,因为读取返回值要⽐读取线程内的errno变量的开销更⼩。
#include <iostream> #include <unistd.h> #include <cstdio> #include <string> #include <cstring> #include <vector> #include <pthread.h>void *routine(void *args) {std::string name = static_cast<char *>(args);int cnt = 10;int a = 0;while(cnt--){std::cout<<"我是一个新线程, 我的name: "<<name<<std::endl;if(cnt == 5){a/=0;}sleep(1);}return nullptr; }int main() {pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");if(n!=0){std::cout<<"Create thread fail: "<<strerror(n)<<std::endl;}while(true){std::cout<<"我是main线程!"<<std::endl;sleep(1);}return 0; }
1.3. 获取线程ID
# include <pthread.h>// 获取线程 IDpthread_t pthread_self ( void );
打印出来的 tid 是通过 pthread 库中有函数 pthread_self 得到的,它返回⼀个 pthread_t 类型的
变量,指代的是调⽤ pthread_self 函数的线程的 “ID”。这里的ID并不是内核中轻量级进(LWP)的ID,而是在库中维护的ID,其实就是线程在库中的起始虚拟地址。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <pthread.h>void *routine(void *args)
{std::string name = static_cast<char *>(args);while(true){std::cout<<"我是新线程: "<<name<<"我的ID是: "<<pthread_self()<<std::endl;sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");if (n != 0){std::cout << "Create thread fail: " << strerror(n) << std::endl;}while (true){std::cout << "我是main线程!" << std::endl;sleep(1);}return 0;
}
运行结果:
1.4. 线程终止
终止线程的三种方法:
从线程函数return。这种⽅法对主线程不适⽤,从main函数return相当于调⽤exit。 线程可以调⽤pthread_ exit终⽌⾃⼰。 ⼀个线程可以调⽤pthread_ cancel终⽌同⼀进程中的另⼀个线程。
pthread_exit函数
功能:线程终⽌原型 :void pthread_exit ( void *value_ptr);参数 :value_ptr:value_ptr不要指向⼀个局部变量。返回值:⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
测试代码:
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <pthread.h>void *routine(void *args)
{std::string name = static_cast<char *>(args);int cnt = 5;while (true){std::cout << "我是新线程: " << name << std::endl;sleep(1);cnt--;if(cnt == 0){pthread_exit(nullptr);}}
}int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");if (n != 0){std::cout << "Create thread fail: " << strerror(n) << std::endl;}while (true){std::cout << "我是main线程!" << std::endl;sleep(1);}return 0;
}
运行结果:
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是⽤malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
pthread_cancel函数
功能:取消⼀个执⾏中的线程原型 :int pthread_cancel ( pthread_t thread);参数 :thread:线程 ID返回值:成功返回 0 ;失败返回错误码
测试代码:
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <string>
#include <pthread.h>void *routine(void *args)
{std::string name = static_cast<char *>(args);while (true){std::cout << "我是新线程: " << name << std::endl;sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");if (n != 0){std::cout << "Create thread fail: " << strerror(n) << std::endl;}int cnt = 5;while (true){cnt--;std::cout << "我是main线程!" << std::endl;sleep(1);if(!cnt){pthread_cancel(tid);}}return 0;
}
测试结果:
1.5. 线程等待
为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复⽤刚才退出线程的地址空间。
功能:
等待线程结束
原型int pthread_join ( pthread_t thread, void **value_ptr);参数 :thread:线程 IDvalue_ptr:它指向⼀个指针,后者指向线程的返回值返回值:成功返回 0 ;失败返回错误码
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
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(void)
{pthread_t tid;void *ret;// thread 1 returnpthread_create(&tid, NULL, thread1, NULL);pthread_join(tid, &ret);printf("thread return, thread id %X, return code:%d\n", tid, *(int *)ret);free(ret);// thread 2 exitpthread_create(&tid, NULL, thread2, NULL);pthread_join(tid, &ret);printf("thread return, thread id %X, return code:%d\n", tid, *(int *)ret);free(ret);// thread 3 cancel by otherpthread_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_CANCELED\n", tid);elseprintf("thread return, thread id %X, return code:NULL\n", tid);
}
运行结果:
1.6. 分离线程
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进⾏pthread_join操作,否则⽆法释放资源,从⽽造成系统泄漏。 如果不关⼼线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃动释放线程资源。
int pthread_detach(pthread_t thread);//可以是线程组内其他线程对⽬标线程进⾏分离,也可以是线程⾃⼰分离:
pthread_detach(pthread_self());
joinable和分离是冲突的,⼀个线程不能既是joinable⼜是分离的。
#include <stdio.h>
#include <stdlib.h>
#include <string.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(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 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;
}
2. 线程ID及进程地址空间布置
pthread_ create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。该线程ID和前⾯说的线程ID不是⼀回事。 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最⼩单位,所以需要⼀个数值来唯⼀表⽰该线程。 pthread_ create函数第⼀个参数指向⼀个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。 线程库NPTL提供了pthread_ self函数,可以获得线程⾃⾝的ID。
pthread_t pthread_self(void);
pthread_t 到底是什么类型呢?取决于实现。对于Linux⽬前实现的NPTL实现⽽⾔,pthread_t类
型的线程ID,本质就是⼀个进程地址空间上的⼀个地址。

3. 线程封装
#pragma once#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>namespace ThreadModule
{// 原⼦计数器,⽅便形成线程名称std::uint32_t cnt = 0;// 线程要执⾏的外部⽅法,我们不考虑传参,后续有std::bind来进⾏类间耦合using threadfunc_t = std::function<void()>;// 线程状态enum class TSTATUS{THREAD_NEW,THREAD_RUNNING,THREAD_STOP};// 线程class Thread{private:static void *run(void *obj){Thread *self = static_cast<Thread *>(obj);pthread_setname_np(pthread_self(), self->_name.c_str()); // 设置线程名称self->_status = TSTATUS::THREAD_RUNNING;if (!self->_joined){pthread_detach(pthread_self());}self->_func();return nullptr;}void SetName(){// 后期加锁保护_name = "Thread-" + std::to_string(cnt++);}public:Thread(threadfunc_t func) : _status(TSTATUS::THREAD_NEW),_joined(true), _func(func){SetName();}void EnableDetach(){if (_status == TSTATUS::THREAD_NEW)_joined = false;}void EnableJoined(){if (_status == TSTATUS::THREAD_NEW)_joined = true;}bool Start(){if (_status == TSTATUS::THREAD_RUNNING)return true;int n = ::pthread_create(&_id, nullptr, run, this);if (n != 0)return false;return true;}bool Join(){if (_joined){int n = pthread_join(_id, nullptr);if (n != 0)return false;return true;}return false;}~Thread() {}private:std::string _name;pthread_t _id;TSTATUS _status;bool _joined;threadfunc_t _func;};
}