欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 高考 > 线程控制学习

线程控制学习

2025/11/9 22:11:03 来源:https://blog.csdn.net/guaiguaiyalj/article/details/144677450  浏览:    关键词:线程控制学习

1、线程创建:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);

参数 thread:返回线程ID;attr:设置线程的属性,attr为nullptr表示使用默认属性(一般这个都是nullptr);start_routine:是个函数地址,线程启动后要执行的函数;arg:传给线程启动函数的参数 返回值:成功返回0;失败返回错误码。

注意上面的start_routine是返回值为void*,参数也是void*。

pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID。pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

在创建线程时要使用到线程库,所以在使用线程库时,会将对应的库文件加载到内存中,然后通过页表和虚拟地址完成映射。最终线程库会被映射到虚拟地址空间的mmap区域。下图所示:

当我们调用pthread_create创建线程后,在线程库中就会新建一个描述线程的结构体,来保存线程的相关数据,而这一部分也是实际的内存,有对应的地址,而pthread_t本质就是一个进程地址空间上的地址。

这就相当于线程的TCB。

通过上面对进程地址空间分布的认识也可以学习一个操作:_thread

对于全局变量,主线程和新线程是共享的,所以当某一个线程访问全局变量时,访问的都是同一个变量,下图可以看到新线程改变g_val,主线程读出的也跟着改变:

但是对全局变量添加_thread修饰后,就将一个内置类型设置为局部存储,这种情况下,虽然这个g_val仍然是全局变量,但是每个线程都有属于自己的一个,互不影响,当在自己的线程中对其改变时,不会影响其他的线程,下图可以看出,新线程改变g_val,但是主线程并没跟着变:

而且通过上面的两个图也可以发现,他们的地址变换很大,因为刚开始,只是一个全局表量,保存在已初始化数据段,而添加_thread修饰后,主线程的g_val就在主线程栈中存储,而新线程的g_val就在对应线程空间的线程栈上。

2、线程退出:

1)新线程函数return;

void *routine(void *args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5;while (cnt){cout << "我是新线程!name: " << td->namebuffer << endl;cnt--;sleep(1);// exit(0); //不能终止线程,是用来终止进程的,这里任何一个执行流调用exit,会让所有的线程和主线程都终止。}//一般终止线程return即可//nullptr;return (void*)100; //1. 线程函数结束,return的时候,线程就算终止了
}

2)线程可以调用pthread_ exit终止自己;

void pthread_exit(void *value_ptr); 参数 value_ptr:value_ptr不要指向一个局部变量。

void *routine(void *args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5;while (cnt){cout << "我是新线程!name: " << td->namebuffer << endl;cnt--;sleep(1);}ThreadReturn* tr = new ThreadReturn();tr->code_quit = td->number;tr->code_result = 1;//注意:返回值需要是void*pthread_exit((void*)tr);
}

注意:pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc/new分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

上面的ThreadReturn是自己定义的一个结构体,用来保存退出信息。这里的返回值是new出来的,在主线程的线程等待时记得delete。

3)一个线程可以调用pthread_ cancel终止同进程中的另一个线程。

int pthread_cancel(pthread_t thread); 参数 thread:线程ID;成功返回0;失败返回错误码。

线程取消要注意:只有运行的线程才能被取消:

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <pthread.h>using namespace std;class ThreadData
{
public:pthread_t tid;char namebuffer[64];int number;
};class ThreadReturn
{
public:int code_quit;int code_result;
};void *routine(void *args)
{ThreadData* td = static_cast<ThreadData*>(args);int cnt = 5;while (cnt){cout << "我是新线程!name: " << td->namebuffer << endl;cnt--;sleep(1);// exit(0); //不能终止线程,是用来终止进程的,这里任何一个执行流调用exit,会让所有的线程和主线程都终止。}return nullptr;
}int main()
{vector<ThreadData*> threads;
#define NUM 10for (int i = 0; i < NUM; ++i){ThreadData* td = new ThreadData();td->number = i+1;snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);pthread_create(&(td->tid), nullptr, routine, td);threads.push_back(td);// sleep(1);}for(auto& iter : threads){cout << "create thread" << iter->namebuffer << " : " << iter->tid << "success" << endl;}//线程取消:pthread_cancel,只有执行中的线程才能被取消sleep(2);for(int i = 0; i < threads.size()/2; ++i){pthread_cancel(threads[i]->tid);cout << "cancel: " << threads[i]->namebuffer << " success!" << endl;}/*for(auto& iter : threads){void* ret = nullptr;//这里的第二个参数的类型需要是void**,因为外面的ret是void*,//如果传ret进去,那么函数内部改变ret并不会影响外面的值,所以需要传地址进去,那么ret的地址就是void**的了。pthread_join(iter->tid, &ret);cout << "join: " << iter->namebuffer << " success! code_quit--> " << (long long)ret << endl;//释放new的资源delete ret;delete iter;}*/cout << "main thread quit!" << endl;return 0;
}

3、线程等待:

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间。如果不等待,会造成类似僵尸进程的问题:内存泄漏。

int pthread_join(pthread_t thread, void **value_ptr); 

参数 thread:线程ID;value_ptr:它指向一个指针,后者指向线程的返回值。

等待成功返回0;失败返回错误码。

1)如果新线程通过return返回,value_ ptr所指向的单元里存放的是新线程函数的返回值。

2)如果新线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED。

3)如果新线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

4)如果对新线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

要注意:这里的value_ptr是输出型参数,用来获取新线程的返回值,因为新线程的返回值是void*的,返回的数据保存在pthread库中;想要同过该函数将返回值带出,就得在外面用个void* 的变量,然后再将该变量的地址(void**)传进去,最终就能拿到新线程的返回值信息。

4、线程分离:

1)默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

2)如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。

所以对分离的线程,就不需要再join了,可以自动释放资源。

int pthread_detach(pthread_t thread);

线程分离函数,参数thread是线程ID。线程ID可以通过pthread_self()来获得。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
using namespace std;void* start_routine(void* args)
{string name = static_cast<const char*>(args);//将自己设置为分离状态,pthread_self()是获取自己的ID// pthread_detach(pthread_self()); int cnt = 5;while(cnt--){cout << name << " running..." << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");//将创建的新线程分离,分离后该线程退出时自动回收pthread_detach(tid);//一个线程默认是joinable的,如果设置了分离状态,就不能够进行等待了。int n = pthread_join(tid,nullptr);cout << n << " : " << strerror(n) << endl;return 0;
}

这里需要注意的是:进行线程分离时,可以在主线程创建后分离,也可以在新线程内部设置分离。

这里先看创建新线程后就分离,如果对分离的线程再等待就会出问题,最终导致程序崩溃:

而在新线程内部分离,可能有一些问题,因为创建新线程后,并不能确定是哪一个线程先运行,可能主线程先运行,已经开始等待新线程,而线程运行时虽然设置分离状态,主线程并不知道,但是主线程仍然在等待,并且等待成功,但是实际上对于已经分离的线程再等待就会出现问题:

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词