在上篇关于线程的文章中,我们已经比较详细的了解了关于线程得概念,以及简单得见识过了线程,本篇文章将对线程概念进行些补充,同时帮助大家实现对线程的控制,如:创建线程,等待线程,取消线程,终止线程。
我们知道线程是可以共享资源的,但是真的是所有资源都共享吗?实则不然,在操作系统中,线程是相对独立的。尽管共享的大部分数据,但是都有其独立的资源。 这些资源在同一进程的不同线程之间是隔离的。每个线程可以独立访问和修改自己的私有资源,而不会影响其他线程的私有资源。 在多线程编程中,线程私有资源的主要作用是解决资源竞争和数据共享的问题。通过将某些资源声明为线程私有,可以避免多个线程同时访问共享资源时引发的竞态条件(Race Condition)和数据不一致问题。 以下是线程私有的资源:
ID
:内核观点中的LWP
.errno
:线程因为错误而终结,需要告知父进程。/**
* 验证线程的私有属性
*/
#include <iostream>
#include <pthread.h>
void* threadFunc(void* arg) {
int localVar = 0;
std::cout << "Thread ID: " << pthread_self()
<< ", Address of localVar: " << &localVar << std::endl;
return nullptr;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, nullptr, threadFunc, nullptr);
pthread_create(&t2, nullptr, threadFunc, nullptr);
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
return 0;
}
//打印结果
/*
Thread ID: 139879076959808, Address of localVar: 0x7f3822b18e34
Thread ID: 139879085352512, Address of localVar: 0x7f3823319e34
*/
可以看到他们的地址是不一样的。
共享资源就比较好理解了,因为线程看到的都是同一块地址空间。 ![[Pasted image 20250117153905.png]] 如图所示。那么线程具体共享哪些资源的呢?
IO
操作时,无需再次打开文件。ID
和组ID
:进程属于某一个组中的某个用户,多线程也是如此。/**
* 验证全局变量的共享性
*/
#include <iostream>
#include <pthread.h>
int sharedVar = 0;
void* threadFunc(void* arg) {
for (int i = 0; i < 5; ++i) {
++sharedVar;
std::cout << "Thread ID: " << pthread_self()
<< ", sharedVar: " << sharedVar << std::endl;
}
return nullptr;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, nullptr, threadFunc, nullptr);
pthread_create(&t2, nullptr, threadFunc, nullptr);
pthread_join(t1, nullptr);
pthread_join(t2, nullptr);
return 0;
}
/*
打印结果
Thread ID: Thread ID: 140233541813824, sharedVar: 140233550206528, sharedVar: 22
Thread ID: 140233550206528, sharedVar: 3
Thread ID: 140233550206528, sharedVar: 4
Thread ID: 140233550206528, sharedVar: 5
Thread ID: 140233550206528, sharedVar: 6
Thread ID: 140233541813824, sharedVar: 7
Thread ID: 140233541813824, sharedVar: 8
Thread ID: 140233541813824, sharedVar: 9
Thread ID: 140233541813824, sharedVar: 10
*/
创建线程,不管是在上一篇文章还是在前面的代码中都有所提及。
创建线程的函数就是pthread_create()
pthread_create
函数是POSIX标准中用于创建新线程的函数,它运行在同一进程中并发执行多个任务。
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数说明:
pthread_t* thread
: pthread_join
)中用于标识线程。const pthread_attr_t* attr
: NULL
使用默认属性。void* (*start_routine)(void*)
: pthread_join
获取。void* arg
: 0
:表示线程创建成功。虽然已经看了很多遍了,但是我还是要用这个函数来给大家演示一个现象。
#include <iostream>
#include <unistd.h>
#include <threads.h>
using namespace std;
void* run(void* arg){
while(true){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
return nullptr;
}
const int num = 5;
int main(){
pthread_t pth[num];
for(int i = 1;i<=num;++i){
char name[64];
snprintf(name,sizeof name,"thread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
while(true){
cout<<"我是主线程: main"<<endl;
sleep(2);
}
return 0;
}
/*
打印结果
我是子线程:thread5
我是子线程:thread5
我是子线程:我是主线程: main
thread5
我是子线程:thread5
我是子线程:thread5
我是子线程:我是子线程:thread5thread5
*/
在这段代码中我创建了5个线程,然后给他们分别命名位thread[1-5]
。但是当我们打印结果后出来的却只有thread5
.
这是为什么呢?
其实这也是线程共享资源造成的错误,name
是在主线程栈区开辟的空间,多个线程实际上指向的是同一块空间,最后一次覆盖后,所有线程就都打印thread5
了。
![[Pasted image 20250122203537.png]]
为了避免这个问题,我们应该区堆上开辟空间。
#include <iostream>
#include <unistd.h>
#include <threads.h>
using namespace std;
void* run(void* arg){
while(true){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
delete[] (char*)arg;
return nullptr;
}
const int num = 5;
int main(){
pthread_t pth[num];
for(int i = 1;i<=num;++i){
char* name = new char[64];
snprintf(name,sizeof name,"thread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
while(true){
cout<<"我是主线程: main"<<endl;
sleep(2);
}
return 0;
}
/*
打印结果:
我是主线程: main
我是子线程:thread2
我是子线程:thread1
我是子线程:thread4
我是子线程:thread3
我是子线程:thread5
我是子线程:我是子线程:thread4thread2
我是子线程:thread1
我是子线程:thread3
*/
这时肯定有人问了,堆区也是共享资源啊,为什么不会被覆盖?
这个就牵扯到计算机对不同空间的处理了。 在第一段代码中,
name
是局部变量,name内存地址是相同的因为栈帧被重复利用了。 而第二段代码中,name
是动态分配的内存,存储在堆上,每次给name
分配的地址是不同的。
我们知道,进程是存在等待机制的,其实线程也是存在等待机制的。
线程等待的存在是为了在多线程程序中协调线程间的执行顺序,确保资源的正确访问和结果的有序生成。它通过协调线程间的执行顺序、确保资源的安全访问、回收线程资源以及实现线程间的通信,保证了多线程程序的稳定性和正确性。
Linux 中的 POSIX 线程库提供了 pthread_join
函数来实现这种等待机制。
pthread_join
函数int pthread_join(pthread_t thread, void **retval);
参数说明
thread
: pthread_t
。pthread_create
创建线程时返回的。retval
: void **
。pthread_exit
的参数。NULL
。
返回值0
: 表示成功等待目标线程结束。ESRCH
: 指定的线程不存在。EINVAL
: 线程不可连接,例如它已经分离。EDEADLK
: 调用线程与被等待线程之间存在死锁。#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run(void* arg){
int cnt = 3;
while(cnt--){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
delete[] (char*)arg;
return nullptr;
}
int main(){
pthread_t pth[5];
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"pthread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
int cnt = 1;
for(int i = 1;i<=5;++i){
if(pthread_join(pth[i-1],nullptr)!=0){
perror("等待失败");
exit(1);
}else{
printf("成功等待%d个线程\n",cnt++);
}
sleep(1);
}
}
/*
打印结果:
我是子线程:pthread1
我是子线程:pthread2
我是子线程:pthread4
我是子线程:pthread3
我是子线程:pthread5
我是子线程:pthread2
我是子线程:pthread3
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:pthread2
我是子线程:pthread3
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
成功等待1个线程
成功等待2个线程
成功等待3个线程
成功等待4个线程
成功等待5个线程
*/
我们知道,终止一个进程的函数是exit
,那么终止一个线程的函数又是什么呢?
在多线程编程中,线程的终止是一个重要的操作。线程可以通过多种方式终止,例如正常执行完毕、显式调用终止函数、或者被其他线程强制终止。
线程终止主要有3种方式,pthread_exit
、pthread_cancel
和正常返回。
pthread_exit
函数void pthread_exit(void *retval);
参数说明:
retval
:
void *
。pthread_join
回收,则通过 pthread_join
的第二个参数获取该值。retval
传递给回收线程。retval
的内存管理由调用者负责,通常在退出前动态分配或者使用静态内存。#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* run(void* arg){
int cnt = 3;
while(cnt--){
cout<<"我是子线程:"<<(char*)arg<<endl;
sleep(1);
}
pthread_exit(arg);
}
int main(){
pthread_t pth[5];
void* ret;
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"pthread%d",i);
pthread_create(&pth[i-1],nullptr,run,name);
}
int cnt = 1;
for(int i = 1;i<=5;++i){
if(pthread_join(pth[i-1],&ret)!=0){
perror("等待失败");
exit(1);
}else{
cout<<"成功等待线程:"<<(char*)ret<<endl;
}
sleep(1);
}
}
/*
打印结果:
我是子线程:pthread3
我是子线程:pthread2
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:pthread3
我是子线程:pthread2
我是子线程:pthread1
我是子线程:pthread4
我是子线程:pthread5
我是子线程:我是子线程:pthread1pthread3
我是子线程:pthread2
我是子线程:pthread4
我是子线程:pthread5
成功等待线程:pthread1
成功等待线程:pthread2
成功等待线程:pthread3
成功等待线程:pthread4
成功等待线程:pthread5
*/
如此一来,我就可以拿到线程的名字了。
注意:因为retval
的类型是void*
,所以甚至我可以返回一个结构体/对象。
pthread_cancel
函数int pthread_cancel(pthread_t thread);
参数说明
thread
: pthread_t
。pthread_create
返回。返回值
int
。0
: 成功向目标线程发送了取消请求。ESRCH
: 目标线程不存在或无效。EINVAL
: 无效的参数,例如未启用线程取消功能。#include <pthread.h>
#include <iostream>
#include <unistd.h>
void* threadFunc(void* arg) {
while (true) {
std::cout << "子线程正在运行...\n";
sleep(1);
}
return nullptr;
}
int main() {
pthread_t thread;
pthread_create(&thread, nullptr, threadFunc, nullptr);
sleep(3);
pthread_cancel(thread); // 发送取消请求
pthread_join(thread, nullptr);
std::cout << "子线程已被取消.\n";
return 0;
}
/*
打印结果:
子线程正在运行...
子线程正在运行...
子线程正在运行...
子线程已被取消.
*/
实战环节,我会用线程来计算[1,100*i](1<=i<=5)
的和。
#include <iostream>
#include <unistd.h>
#include <string>
#include <pthread.h>
using namespace std;
enum Status{//枚举状态
OK=0,
ERROR
};
/*
创建一个线程数据类,其中的属性有线程名字,线程id,线程状态,线程准备计算和的右边界。
*/
class ThreadData{
public:
ThreadData(const string& name,int n,int id)
:_name(name),
_n(n),
_id(id),
_result(0),
_status(Status::OK)
{}
~ThreadData(){}
public:
string _name;
int _n;
int _id;
int _result;
Status _status;
};
void* run(void* arg){
ThreadData* td = static_cast<ThreadData*>(arg);
for(int i = 0;i<=td->_n;++i){
td->_result+=i;
}
cout<<"线程: "<<td->_name<<"执行完毕"<<endl;
pthread_exit(arg);
}
int main()
{
pthread_t pths[5];
//创建线程
for(int i = 1;i<=5;++i){
char* name = new char[64];
snprintf(name,64,"thread-%d",i);
ThreadData* threadData = new ThreadData(name,i*100,i);
pthread_create(&pths[i-1],nullptr,run,threadData);
sleep(1);
}
void* retval = nullptr;
//等待线程
for(int i = 1;i<=5;++i){
if(pthread_join(pths[i-1],&retval)!=0){
perror("线程等待失败!");
exit(1);
}
ThreadData* td = static_cast<ThreadData*>(retval);
if(td->_status == Status::OK){
printf("线程%s,计算[1~%d]的结果为:%d\n",td->_name.c_str(),td->_n,td->_result);
}
delete td;
}
cout<<"所有线程退出完毕"<<endl;
return 0;
}
/*
打印结果:
线程: thread-1执行完毕
线程: thread-2执行完毕
线程: thread-3执行完毕
线程: thread-4执行完毕
线程: thread-5执行完毕
线程thread-1,计算[1~100]的结果为:5050
线程thread-2,计算[1~200]的结果为:20100
线程thread-3,计算[1~300]的结果为:45150
线程thread-4,计算[1~400]的结果为:80200
线程thread-5,计算[1~500]的结果为:125250
所有线程退出完毕
*/
程序可以正常运行,各个线程也能正常计算出结果;这里只是简单累加求和,线程还可以用于其他场景。比如:网络传输、密集型计算、多路IO
等等,无非就是修改线程的业务逻辑。
线程控制是多线程编程中不可或缺的一部分,通过合理管理线程的创建、运行、同步和终止,可以提升程序的效率、稳定性和资源利用率。在实际开发中,理解线程的生命周期、掌握线程间通信与同步机制,以及灵活运用线程控制函数如 pthread_create、pthread_exit 和 pthread_join,能够帮助开发者更好地实现并发编程的目标。同时,线程控制的精髓不仅在于技术的掌握,更在于对资源的合理调配和对任务的高效分配。掌握这些技巧,无疑将为构建高效、可靠的多线程程序打下坚实的基础。
往期专栏:Linux专栏:Linux