线程控制的相关接口
进程创建相关
之前我们已经认识到了pthread_create函数用来创建线程,这里不再赘述。
pthread_self函数
void* routine(void* args)
{std::cout << "我是新线程..." << pthread_self() << std::endl;return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");while(true) sleep(1);return 0;
}
通过结果我们可以发现,得到了一个数字。实际上,使用 pthread_self 得到的这个数实际上是虚拟地址空间上的一个地址,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性;而真正的线程ID是通过 ps -aL | head -1 && ps -aL | grep mythread 命令得到的。
进程等待相关
为什么需要线程等待?
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
- 若不等待,就会有内存泄露的问题。
pthread_join函数
参数说明
- pthread_t thread:这是要等待的线程的标识符(ID),该标识符是由 pthread_create 函数返回的。
- void **retval:这是一个指向 void * 指针的指针,用于接收被等待线程的返回值。
retval参数的可能取值(下面代码中都会体现):
- 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_ cancel异常终掉,retval所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
如果成功,pthread_join 返回 0;如果失败,则返回错误码。
ps. 如果需要退出的线程一直不退出,那么等待该线程的线程就会一直等待!
void* routine(void* args)
{std::cout << "我是新线程..." << pthread_self() << std::endl;return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");// 等待int n = pthread_join(tid1, nullptr);if(n != 0)std::cerr << "join error: "<< n << ", " << strerror(n) << std::endl;std::cout << "join success!" << std::endl;while(true) sleep(1);return 0;
}
我们将代码稍作修改
void* routine(void* args)
{while(true){std::cout << "我是新线程..." << toHex(pthread_self()) << std::endl;sleep(1);break;}// 返回值:可以是变量、数字、对象!return (void*)10; // 将返回结果修改为10
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");void *ret = nullptr;int n = pthread_join(tid, &ret);// 等待成功并打印ret的值std::cout << "join success!, ret: " << (long long int)ret << std::endl;return 0;
}
结论:ret的值其实就是routine的返回值!
ps. 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!栈空间也是如此!
Demo代码
// 创建多线程并等待的样例(传递参数为对象)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <pthread.h>#define NUM 5class ThreadDate
{
public:ThreadDate(){}void Init(int a, int b, std::string name){ _a = a;_b = b;_name = name;}int Result() {return _a + _b; }std::string Name(){return _name;}void SetId(pthread_t id){ _tid = id;}pthread_t Id(){return _tid;}int A(){return _a;}int B(){return _b;}~ThreadDate(){}
private:int _a;int _b;int _result;std::string _name;pthread_t _tid;
};void* routine(void* args)
{ThreadDate* td = static_cast<ThreadDate*>(args);std::cout << "我是新线程,我的名字是: " << td->Name() << std::endl;return nullptr;
}
int main()
{ThreadDate td[NUM];// 预处理for(int i = 0;i < NUM;i++){char id[64];snprintf(id, sizeof(id), "thread-%d", i);td[i].Init(i * 10, i * 20, id);}// 创建多个线程for(int i = 0;i < NUM;i++){pthread_t tid;pthread_create(&tid, nullptr, routine, &td[i]);td[i].SetId(tid);}// 等待多个线程for(int i = 0;i < NUM;i++){pthread_join(td[i].Id(), nullptr);}// 汇总处理结果for(int i =0;i < NUM; i++){printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());}return 0;
}
进程终止模块
如果需要只终止某个线程而不终止整个进程,有三种方法:
- 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
- 线程可以调用pthread_ exit终止自己。
- 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
pthread_exit函数
参数:
retval
:这是一个指向任意数据的指针,该数据将被线程的终止状态所使用,并且可以被其他线程通过调用pthread_join
来访问。
void* routine(void* args)
{std::string name = static_cast<const char*>(args);std::cout << "我是新线程..." << std::endl;pthread_exit((void*)10); // 作用与return相同
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");void* ret = nullptr;pthread_join(tid, &ret);std::cout << "new thread exit code: " << (long long int)ret << std::endl;return 0;
}
pthread_cancel函数
参数:
thread
:要发送取消请求的线程标识符(pthread_t 类型)。
void* routine(void* args)
{std::string name = static_cast<const char*>(args);while(true){std::cout << "我是新线程..." << std::endl;sleep(1);}//该线程没有退出
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");sleep(2);pthread_cancel(tid);std::cout << "取消线程: " << tid << std::endl;sleep(2);void* ret = nullptr;// 取消之后的线程的返回值为:-1:PTHREAD_CANCELED ((void *) -1)// 所以取消之后的线程也必须join,否则就会内存泄漏pthread_join(tid, &ret); std::cout << "new thread exit code: " << (long long int)ret << std::endl;return 0;
}
线程分离模块
pthread_detach函数
void* routine(void* args)
{// 线程分离可以自己主动分离// pthread_detach(pthread_self());std::string name = static_cast<const char*>(args);while(true){std::cout << "我是新线程..." << std::endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, (void*)"thread");// 线程分离也可以被主线程分离pthread_detach(tid);sleep(2); //先让线程分离,在进行等待void* ret = nullptr;int n = pthread_join(tid, &ret);// pthread_join返回0代表等待成功,非0为失败std::cout << "new thread exit code: " << (long long int)ret << ", n: " << n << std::endl;return 0;
}
线程分离之后,pthread_join就不会阻塞等待被分离的线程,所以join就会失败。
线程局部存储
线程局部存储(Thread Local Storage,TLS)是一种特殊的存储机制,用于为每个线程提供独立的变量副本,确保线程之间的数据隔离。
__thread
关键字实现线程局部存储
__thread int tls_variable = 0; // 每个线程都有独立的tls_variable副本
ps. __thread只能修饰内置类型
优点:
线程安全:每个线程访问自己的变量副本,避免了线程之间的数据竞争。
性能优化:减少了锁的使用,提高了多线程程序的性能。
灵活性:可以存储线程相关的上下文信息。
缺点:
内存占用:每个线程都会有一个独立的变量副本,可能会增加内存占用。
线程生命周期管理:需要确保线程结束时清理线程局部存储的资源,否则可能导致内存泄漏。