一、线程间的全局变量共享
上节课我们讲到,线程是共享一个地址空间的,所以对于全局变量,多个线程访问的一定是同一个全局变量。
这里提出一个疑问,既然线程是共享一个地址空间,那么为什么多线程之间为什么不能访问别人的的局部变量呢,答案是没有它们的地址。 但是如果我们通过一个定义一个全局变量来保存别人的局部变量的地址,其实也是可以访问到别的线程的局部变量,不过我们不推荐这么做。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
int count = 0;
void *threadRun(void *args)
{
while (1)
{
count++;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
while (1)
{
std::cout << "count : " << count << std::endl;
sleep(1);
}
return 0;
}
二、线程等待
与进程等待类似,当主线程还在运行时,其他新线程已经完成了它们的工作,我们的主线程也要等待我们的新线程来进行资源回收,避免类似于进程僵尸的情况,造成内存泄露问题。
参数 pthread_t thread 是线程id,是该你需要等待的线程。
参数 void** retval 是一个输出型参数,用于接收新线程退出时的返回值。
返回值:成功返回0;失败返回错误码
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
void *threadRun(void *args)
{
//pthread_self()用于返回自己的tid
printf("%s : %p\n", (char *)args, pthread_self());
// 为更好观察,休息5s
sleep(5);
return (void *)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
sleep(5);
int ret;
pthread_join(tid, (void**)&ret);
std::cout << "main thread: " << ret << std::endl;
sleep(5);
return 0;
}
pthread_self和tid
在上面的示例代码中,我们使用了pthread_slef函数用于返回该线程自身的线程id,可是为什么运行结果输出的并不是我们使用ps -aL命令所显示的LWP呢?
这是因为,我们所使用的pthread属于第三方库,在linux中又叫做原生线程库,对于多线程,我们也需要先描述再组织,而描述者应该是pthread库,所以pthread库中才含有我们的多线程的数据结构,而我们又学习过进程地址空间的分布。
我们的动态库的地址是被加载到共享区的,而pthread_slef所返回的tid其实是被映射的struct pthread的起始地址。
回到我们的线程等待,我们的pthread_join并不能像进程等待一样有非阻塞选项,这就说明一旦我们开始使用pthread_join开始等待新线程,如果新线程没有退出,我们的主线程就要一直被阻塞。
三、线程异常
当多线程进程执行时,无论哪个线程出现了异常,整个进程都会一起崩溃。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
void *threadRun(void *args)
{
printf("%s : %p\n", (char *)args, pthread_self());
// 为更好观察,休息5s
sleep(5);
int a = 10;
a /= 0;
return (void *)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
sleep(5);
while (1);
return 0;
}
四、线程退出
在多线程进程中,我们在代码中应该尽量不使用exit()退出,因为exit是进程退出,任何一个线程调用此函数,都会使整个进程退出!
而如果我们想线程退出而不是进程退出,我们可以使用pthread_exit。
参数void* retval就是用于代替回调函数的返回值。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
void *threadRun(void *args)
{
printf("%s : %p\n", (char *)args, pthread_self());
// 为更好观察,休息5s
sleep(5);
pthread_exit((void*) 11);
return (void *)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
sleep(5);
int ret;
pthread_join(tid, (void**)&ret);
std::cout << "main thread: " << ret << std::endl;
sleep(5);
return 0;
}
五、线程取消
返回值:成功返回0;失败返回错误码
调用此函数,可以取消tid为thread的线程。
需要注意的是如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_
CANCELED。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
void *threadRun(void *args)
{
printf("%s : %p\n", (char *)args, pthread_self());
// 为更好观察,休息5s
sleep(5);
// pthread_cancel(pthread_self());
return (void *)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
sleep(5);
pthread_cancel(tid);
int ret;
pthread_join(tid, (void **)&ret);
std::cout << "PTHREAD_CANCELED : " << (int)PTHREAD_CANCELED << std::endl;
std::cout << "main thread: " << ret << std::endl;
sleep(5);
return 0;
}
六、线程分离
当我们并不关心新线程退出后的退出码,不想使用pthread_join来阻塞主线程,又不想让退出后的新线程因为没有被等待导致内存空间泄露的问题,我们就可以让新线程进行分离,分离后,子线程退出时将自动释放资源。
一般调用此函数都是在新线程,让新线程自己分离自己,不过也可以分离别人。
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <pthread.h>
#include <string.h>
void *threadRun(void *args)
{
pthread_detach(pthread_self());
printf("%s : %p\n", (char *)args, pthread_self());
sleep(5);
return (void *)10;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
sleep(3);
int ret;
int n = pthread_join(tid, (void **)&ret);
std::cout << "PTHREAD_CANCELED : " << (int)PTHREAD_CANCELED << std::endl;
std::cout << "main thread: " \
<< "n : " << n << " error: " << strerror(n) << std::endl;
sleep(5);
return 0;
}
pthread_join失败