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