目录
一、概念
二、线程函数
1.pthread_create
2.pthread_exit
3.pthread_join
4.pthread_cancel
三、线程的使用
1.线程的基本操作
2.理解并发运行
一、概念
- 线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。
- 根据运行环境和调度者的身份,县城可分为内核线程和用户线程。
- 线程运行在内核空间,由内核来调度。
- 当进程的一个内核线程获得CPU的使用权时,他就加载并执行一个用户线程
- 线程库负责管理所有执行线程,比如线程的优先级、时间片等。线程库利用longjmp来切换线程的执行,使他们看起来像”并发“执行,但实际内核仍然是把整个进程作为最小单位来调度。
- 一个进程的所有执行线程共享该进程的时间片,他们对外表现出相同优先级。
- 线程的实现方式分为三种模式:完全在用户空间实现、完全由内核调度和双层调度。
常问:进程与线程的区别是?
- 进程:一个正在运行的程序 ,程序执行完,系统进行回收
- 线程:进程内部的一条执行路径(序列)
- 不同语言不同平台上线程的实现机制有送不同
二、线程函数
头文件:
#include<pthread.h>
1.pthread_create
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
- 作用:创建一个线程
- 参数:
- 第一个参数thread是新线程的标识符,后续pthread_*函数通过它来引用新进程。其类型的pthread_t定义为:
-
#include<bits/pthreadtypes.h> typedef unsigned long pthread_t;
- 其中可见,pthread_t是一个整型类型。
-
- 第二个参数attr用于设置新线程的属性。传递NULL表示使用默认线程属性
- 第三个参数是返回值、参数变量都为void*的函数指针
- 第四个参数arg表示新线程的参数
- 第一个参数thread是新线程的标识符,后续pthread_*函数通过它来引用新进程。其类型的pthread_t定义为:
- 返回值:成功时返回0,失败时返回错误码
2.pthread_exit
线程一旦被创建好,内核就可以调度内核线程来执行start_rountine函数指针所指向的函数。线程函数在结束时最好调用如下函数:
void pthread_exit(void* retval);
该函数通过retval参数向进程的回收者传递其退出信息。
3.pthread_join
int pthread_join(pthread_t thread,void**retval);
- 作用:来回收其他线程(前提是目标线程可回收),即等待其他线程结束,类似于wait的使用
- 一个进程中的所有线程都可以调用pthread_join函数
- 该函数会一直被阻塞,知道被回收的线程结束为止。
- 参数:
- thread是目标现成的标识符
- retval是目标线程返回的退出信息
- 返回值:成功0,失败返回错误码
4.pthread_cancel
int pthread_cannel(pthread_t thread);
- 作用:终止一个线程,即取消线程
- 参数:
- thread是目标线程标识符
- 返回值:成功0,失败返回错误码
三、线程的使用
1.线程的基本操作
通过下面代码理解,首先主函数内执行一个主线程,fun函数执行一个线程,两线程一起执行:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void* fun(void* arg)
{
for(int i=0;i<5;i++)
{
printf("fun run\n");
sleep(1);
}
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<2;i++)
{
printf("main run\n");
sleep(1);
}
exit(0);
}
编译执行时,如果不在执行文件后面加上指定库名''-lpthread'',就会出现如下错误:
执行结果:
执行结果可以得到一个结论:
主函数先结束,线程也跟着结束(fun只输出了4次)线程先结束,主函数不会结束。所以,为了使主函数”活到“最后,需要添加pthread_join函数阻塞。再添加pthread_exit对main函数返回一个值:
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void* fun(void* arg)
{
for(int i=0;i<5;i++)
{
printf("fun run\n");
sleep(1);
}
pthread_exit("fun over\n");
}
int main()
{
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<2;i++)
{
printf("main run\n");
sleep(1);
}
char* s=NULL;
pthread_join(id,(void**)&s);
printf("s=%s",s);
exit(0);
}
执行结果如下,主函数先结束,然后阻塞住,知道线程也执行结束(输出5次),然后线程结束后向主函数发送一个”fun over“传递其退出信息。
2.理解并发运行
并发与并行:
并行:任意时刻,两条程序都在执行(多个处理器)
并发:一个处理器,抢占式方式执行
并行执行:
通过创建5个线程,每个线程打印出自己是第几个创建的来观察并行执行的执行结果
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
void* fun(void* arg)
{
int index=*(int*)arg;
for(int i=0;i<3;i++)
{
printf("index=%d\n",index);
sleep(1);
}
}
int main()
{
pthread_t id[5];
int i;
for(i=0;i<5;i++)
{
pthread_create(&id[i],NULL,fun,(void*)&i);
}
for(i=0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
每一次的执行结果都会有差异,甚至不是连续01234的数字输出,是因为并发执行是对资源共同操作,当一个线程还在执行index++=2时,还是执行出来index=2的结果,另一个线程对index=1的值进行执行。
并发执行:
通过创建5个线程,每个线程打印出i++,来观察并行执行的执行结果
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int m_val=1;
void* fun(void* arg)
{
for(int i=0;i<2;i++)
{
printf("m_val=%d\n",m_val++);
}
}
int main()
{
pthread_t id[5];
int i;
for(i=0;i<5;i++)
{
pthread_create(&id[i],NULL,fun,(void*)&i);
}
for(i=0;i<5;i++)
{
pthread_join(id[i],NULL);
}
exit(0);
}
执行结果如下,并发执行的结果每次都一样,最终输出值为5个进程,每个打印2次val,最终m_val=10