目录
一.线程的概念
二.操作系统中线程的实现
三.Linux中线程的实现
四.进程与线程的区别
五.线程的接口相关函数
5.1 pthread_create
5.2 pthread_join
5.3 pthread_exit
六.代码演示
七.如何解决上述问题?
方案1.
方案2.
方案3.
一.线程的概念
进程是一个动态的概念,就是一个程序正在执行的过程。线程就是进程内部的一条执行路径,或者一个执行序列。
二.操作系统中线程的实现
在操作系统中,线程的实现有以下三种方式:
- 内核级线程:开销大,但可以使用多处理器资源,实现真正意义上的并行。
- 用户级线程:开销小,但无法使用多处理器资源。
- 组合级线程
三.Linux中线程的实现
Linux实现线程的机制非常独特,从内核的角度来讲,它并没有线程这个概念;
Linux把所有的线程都当作进程来实现。
内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。
相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和其他一些进程共享某些资源,如地址空间)。
四.进程与线程的区别
1.进程是资源分配的最小单位,线程是CPU调度的最小单位;
2.进程有自己的独立地址空间,线程共享进程中的地址空间;
3.进程的创建消耗资源大,线程创建相对消耗小;
4.进程的切换开销大,线程的切换开销相对较小
五.线程的接口相关函数
5.1 pthread_create
用于创建线程,成功返回0,失败返回错误码。
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);
thread:接收创建的线程的ID;
attr:指定线程的属性,一般不设置线程属性为NULL;
start_routine:指定线程函数,这个线程函数的参数为void*,返回值也为void*;这是一个函数指针;
arg:给线程函数传递的参数(线程刚启动,线程函数的参数为void*,给它传参就是void*)
5.2 pthread_join
等待thread指定的线程退出,线程未退出时,该方法阻塞。(有点像父进程等待子进程结束的wait,或者说合并线程)
int pthread_join(pthread_t thread, void **retval);
retval:接收thread线程退出时,指定的退出信息。
5.3 pthread_exit
退出线程
int pthread_exit(void* retval);
retval:指定退出信息。
六.代码演示
void* thread_fun(void* arg)
{
printf("hello fun!\n");
}
int main()
{
pthread_t id;//线程的id
pthread_create(&id,NULL,thread_fun,NULL);
printf("hello main!\n");
exit(0);
}
注:id开始是没有值的,执行pthread_create之后填充值;
运行结果:
此进程中包含两条线程,两条线程同时执行,有可能先 printf("hello main!\n");之后退出进程,printf("hello fun!\n");还未来得及执行;
有可能先printf("hello fun!\n");再printf("hello main!\n");
七.如何解决上述问题?
方案1.
使用pthread_join函数,这个是等待线程结束或者说合并线程。
我们让子线程循环10次,让主线程循环5次;
我们在主函数完成了自己想要做完的事情以后,调用pthread_join函数;
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
void* thread_fun(void* arg)
{
for(int i=0;i<10;i++)
{
printf("hello fun!\n");
sleep(1);
}
pthread_exit("thread_fun over!\n");
}
int main()
{
pthread_t id;//线程的id
pthread_create(&id,NULL,thread_fun,NULL);
for(int i=0;i<5;i++)
{
printf("hello main!\n");
sleep(1);
}
char* s=NULL;
pthread_join(id,(void**)&s);//不传递退出信息时,第二个参数可以用NULL进行占位
printf("join:s=%s\n",s);
exit(0);
}
运行结果:
利用pthread_join函数带出一个全局变量的值:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
int data=200;
void* thread_fun(void* arg)
{
for(int i=0;i<10;i++)
{
printf("hello fun!\n");
sleep(1);
}
//pthread_exit("thread_fun over!\n");
pthread_exit((void*)&data);
}
int main()
{
pthread_t id;//线程的id
pthread_create(&id,NULL,thread_fun,NULL);
for(int i=0;i<5;i++)
{
printf("hello main!\n");
sleep(1);
}
//char* s=NULL;
//pthread_join(id,(void**)&s);//不传递退出信息时,第二个参数可以用NULL进行占位
//printf("join:s=%s\n",s);
int* p=NULL;
pthread_join(id,(void**)&p);
printf("join data=%d\n",*p);
exit(0);
}
可以返回NULL,字符串,全局变量的地址,不能返回临时变量的地址。
注:
1.其实就是s指向子线程退出的字符串,类似于主线程获取子线程的退出信息;&s也可,就是不强转也可以,但是会有警告;也就是通过一个指针,去记录子线程返回的信息。
2.不接收子线程结束的信息,传NULL即可;
子线程:pthread_exit(NULL);
主线程:pthread_join(id,NULL);
3.pthread join执行的时候会阻塞
4.当我们去等待一个子线程的结束,亦会释放它相应的资源.join会接收子线程反馈给主函数的信息,同时会释放子线程的有关资源.
5.不一定只创建一个子线程,可以创建多个子线程;
6..当然,主线程也可以不调用pthread_join,那么只要在主线程中继续做自己的事情就可以了;即非必须调用pthread_join;
如下面两个方案:
方案2.
把线程函数和主函数改为打印10次,加上sleep便于观察。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
void* thread_fun(void* arg)
{
for(int i=0;i<10;i++)
{
printf("hello fun!\n");
sleep(1);
}
}
int main()
{
pthread_t id;//线程的id
pthread_create(&id,NULL,thread_fun,NULL);
for(int i=0;i<10;i++)
{
printf("hello main!\n");
sleep(1);
}
exit(0);
}
运行结果:
把线程函数改为打印5次,主函数改为打印10次,加上sleep便于观察。
主函数打印10次,线程函数打印5次。
把线程函数改为打印10次,主函数改为打印5次,加上sleep便于观察。
主函数打印5次,线程函数打印次数不定(多于5次)。
所以一般来讲,我们会让主程序就是main程序运行到最后再结束,哪怕主程序什么都不干,也要让它去等待子函数结束。
方案3.
不要退出进程,退出线程(这种方法不太好)
使用pthread_exit函数;主函数里面加一句:
pthread_exit(NULL);
那么子线程就不会结束。(加不加exit(0)都一样)。
为什么说这个方法不太好?因为这个函数是退出线程的,我们通常将它用在子线程中;
子线程结束,就算没有调用pthread_exit(NULL),也不影响主线程的运行,因为主线程结束以后,系统会默认调用exit(0);
注:线程是进程里面的一条执行路径,进程结束了,线程自然也就结束了;多进程的时候,父进程,子进程各自退出是没有影响对方的。