目录
0. 线程的概念
1. 线程的基本操作
1.1 线程的创建:pthread_create函数
1.2 线程等待:pthread_join函数
1.3 线程的分离:pthread_detach函数
1.4 线程退出:pthread_exit函数
1.5 线程的取消:pthread_cancel函数
1.6 线程取消状态:pthread_setcancelstate函数
1.7 线程的取消点:pthread_testcancel函数
1.8 线程的取消类型:pthread_setcanceltype函数
1.9 线程退出清理函数:pthread_cleanup_push和pthread_cleanup_pop函数
1.9.1 调用pthread_exit函数时,系统自动调用线程清理函数
1.9.2 线程被取消时,系统自动调用线程清理函数
1.9.3 调用pthread_cleanup_pop函数时,系统自动调用线程清理函数
总结:
0. 线程的概念
线程的概念
- 每一个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。
- 为了减少开销,从进程中演化出了线程。
- 线程存在于进程中,共享进程的资源。
- 线程是进程中的独立控制流,由环境(包括寄存器组和程序计数器)和一系列的执行指令组成。
- 每一个进程有一个地址空间和一个控制线程。
线程和进程的比较
调度:
线程是CPU调度和分派的基本单位。
拥有资源:
进程是系统中程序执行和资源分配的基本单位。
线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备等)。
系统开销:
同一个进程中的多个线程可共享同一地址空间,因此它们之间的同步和通信的实现也变得比较容易。
在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量。
并发性:
不仅进程间可以并发执行,在一个进程中的多个线程之间也可以并发执行。
总结:
一般把线程称之为轻量级的进程。
一个进程可以创建多个线程,多个线程共享一个进程的资源。
每一个进程创建的时候系统会给其4G虚拟内存,3G用户空间是私有的,所以进程切换时,用户空间也会切换,所以会增加系统开销,而一个进程中的多个线程共享一个进程的资源,所以线程切换时不用切换这些资源,效率会更高。
线程的的调度机制和进程是一样的,多个线程来回切换运行。
多线程的用处:
多任务程序的设计:
一个程序可能要处理应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
并发程序设计:
一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合的,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
网络程序设计:
为提高网络的利用效率,我们可能使用多线程,多每一个连接使用一个线程去处理。
数据共享:
- 同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。
- 在多CPU系统中,实现真正的并行。
1. 线程的基本操作
- 就像每个进程都有一个进程号一样,每个线程也有一个线程号。
- 进程号在整个系统中是唯一的,但线程号不同,线程好只在它所属的进程环境中有效。
- 进程号用pid_t数据类型表示,是一个非负整数。线程号则用pthread_t数据类型来表示。
- 有的系统在实现pthread_t的时候,用一个结构体来表示,所以在可移植的操作系统实现不能不能把它作为整数处理。
1.1 线程的创建:pthread_create函数
pthread_create函数:
#include<pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
功能:
创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非0
与fork不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指定的函数开始运行,该函数运行完后,该线程也就退出了。
线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。
线程函数的程序在pthread库中,故链接时要加上参数-lpthread
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void* thread_fun(void* arg)
{
printf("son1 if running\n");
}
int main()
{
printf("man if running \n");
pthread_t thread;
if (pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
while (1);
return 0;
}
执行截图:
线程的调度机制的验证
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* pthread_fun1(void* arg)
{
printf("son1 if running\n");
sleep(1);
printf("*****************************\n");
}
void* pthread_fun2(void* arg)
{
printf("son2 if running\n");
sleep(1);
printf("----------------------------\n");
}
int main()
{
pthread_t thread1, thread2;
if (pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if (pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
while (1);
return 0;
}
执行截图:
线程处理函数的传参
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int num = 100;
void* pthread_fun1(void* srg)
{
printf("son1:num = %d\n", num);
num++;
int n = *(int*)srg;
printf("son1 n = %d\n", n);
}
void* pthread_fun2(void* arg)
{
sleep(1);
printf("son2:num =%d\n", num);
int n = *(int*)arg;
printf("son2 n = %d\n", n);
}
int main()
{
pthread_t thread1, thread2;
int a = 555;
if (pthread_create(&thread1, NULL, pthread_fun1, (void*)&a) != 0)
{
perror("'fail too pthred_creat");
exit(1);
}
if (pthread_create(&thread2, NULL, pthread_fun2, (void*)&a) != 0)
{
perror("fail to pthread_crateee");
exit(1);
}
while (1);
return 0;
}
执行截图:
1.2 线程等待:pthread_join函数
pthread_join函数
#include<pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待子线程结束,并回收子线程资源。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功:0
失败:非0
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* thread_fun(void* arg)
{
printf("son is running\n");
sleep(3);
printf("son will quit\n");
}
int main()
{
printf("man is running\n");
pthread_t thread;
if (pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthhread_create");
exit(1);
}
if (pthread_join(thread, NULL) != 0)
{
perror("fail toooo pthread_join");
exit(1);
}
printf("man will quit\n");
return 0;
}
执行截图:
获取子线程退出状态值
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* thread_fun(void* arg)
{
static int num = 666;
printf("son is running\n");
sleep(3);
printf("son will quit\n");
return (void*)#
}
int main()
{
printf("man is running\n");
pthread_t thread;
if (pthread_create(&thread, NULL, thread_fun, NULL) != 0)
{
perror("fail to pthhread_create");
exit(1);
}
int* num;
if (pthread_join(thread, (void**)&num) != 0)
{
perror("fail toooo pthread_join");
exit(1);
}
printf("ret_val = %d\n", *num);
printf("man will quit\n");
return 0;
}
执行截图:
1.3 线程的分离:pthread_detach函数
线程的结合态和分离态
Linux线程执行和Windows不同,pthread有两种状态:
可结合的(joinable)或者时分离的(detached),线程默认创建为可结合态。
如果线程时joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用 了pthread_join之后这些资源才会被释放。
如果是detached状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放,使用pthread_detach函数将线程设置为分离态。
创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了线程分离函数:
pthread_detach函数
#include<pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,使其成为一个独立的线程,该线程终止时,系统将自动回收它的资源。
参数:
thread:线程号。
返回值:
成功:0
失败:非0
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
printf("son is running\n");
sleep(2);
printf("son will quite\n");
}
int main()
{
printf("man is running\n");
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create\n");
exit(1);
}
//这里就不需要再执行pthread_join了
if (pthread_detach(thread) != 0)
{
perror("fail to pthread_detach\n");
exit(1);
}
while (1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
执行截图:
1.4 线程退出:pthread_exit函数
在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
- 线程从执行函数中返回
- 线程调用pthread_exit退出线程
- 线程可以被同一进程中的其他线程取消
线程退出函数:pthread_exit函数
#include<pthread.h>
void pthread_exit(void *retval);
功能:
退出调用线程。
参数:
retval:存储线程退出状态的指针。
注:
一个进程中的多个线程时共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
如果要释放资源,结合态要通过pthread_join函数,分离态则自动释放。代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
static char buf[] = "this thread has quied";
printf("son if running\n");
int i = 0;
for (i = 0; i < 10; i++)
{
if (i == 5)
{
// pthread_exit(NULL);
pthread_exit(buf);
}
printf("**********************\n");
sleep(1);
}
}
int main()
{
printf("man is running\n");
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
char* str;
pthread_join(thread, (void**)&str);
printf("str = %s\n", str);
printf("man will quit\n");
return 0;
}
执行截图:
1.5 线程的取消:pthread_cancel函数
取消线程是指取消一个正在执行线程的操作。
#include<pthread.h>
int pthread_cancel(pthread_t thread);
功能:
取消线程。
参数:
thread:目标线程ID。
返回值:
成功:0
失败:出错编号
pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。
此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。
然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决定线程能否被取消以及何时被取消。
线程的取消状态:
即线程能不能被取消。
线程的取消点:
即线程被取消的地方。
线程的取消类型:
在线程能被取消的状态下,是立马被取消还是执行到取消点的时候被取消结束。
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
while (1)
{
printf("son is running\n");
sleep(1);
}
}
int main()
{
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
sleep(3);
//任意线程都可以尝试取消
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
执行截图:
1.6 线程取消状态:pthread_setcancelstate函数
在Linux系统下,线程默认可以被取消,编程时可以通过pthread_setcancelstate函数设置线程是否可以被取消
#include<pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
功能:
设置线程是否可以被取消。
参数:
state:新的状态。
PTHREAD_CANCEL_DISABLE:不可以被取消。
PTHREAD_CANCEL_ENABLE:可以被取消。
oldstate:保存调用线程原来的可取消状态的内存地址。
返回值:
成功:0
失败:非0
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
// pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
while (1)
{
printf("son is running\n");
sleep(1);
}
}
int main()
{
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
执行截图:
1.7 线程的取消点:pthread_testcancel函数
线程被取消后,该线程并不是马上终止,默认情况下线程执行到取消点时才能被终止。编程时可以通过pthread_testcancel函数设置线程的取消点。
void pthread_testcancel(void);
#include<pthread.h>
void pthread_testcancel(void);功能:设置线程的取消点。
参数:无
返回值:无
当别的线程取消调用此函数的线程时候,被取消的线程执行到此函数时结束。
POSIX. 1保证线程调用表1、表2中的任何函数时,取消点都会出现。
表1:
表2:
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
while (1)
{
printf("son is running\n");
sleep(1);
pthread_testcancel();
}
}
int main()
{
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
执行截图:
1.8 线程的取消类型:pthread_setcanceltype函数
线程被取消后,该线程并不是马上终止,默认情况下线程执行到取消点时才能被终止。编程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。
#include<pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
功能:
type:类型
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消。
PTHREAD_CANCEL_DEFERRED:不立即取消。
oldtype:保存调用线程原来的可取消类型的内存地址。
返回值:
成功:0
失败:非0
代码示例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void* pthread_fun(void* arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
while (1)
{
printf("son is running\n");
sleep(1);
}
}
int main()
{
pthread_t thread;
if (pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
sleep(3);
pthread_cancel(thread);
pthread_join(thread, NULL);
return 0;
}
执行截图:
1.9 线程退出清理函数:pthread_cleanup_push和pthread_cleanup_pop函数
和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程清理处理程序(thread cleanup handler)。
注意:
线程可以建立多个清理处理程序。
处理程序在栈中,故它们的执行顺序与它们的注册时顺序相反。
#include<pthread.h>
void pthread_cleanup_push(void (* routine)(void *), void*arg);
功能:
将清除函数压栈。即注册清理函数。
参数:
routine:线程清理函数的指针。
arg:传给线程清理函数的参数。
弹出清理函数
#include<pthread.h>
void pthread_cleanup_pop(int execute);
功能:
将清除函数弹栈,即删除清理函数。
参数:
execute:线程清理函数执行标志位。
非0,弹出清理函数,执行清理函数。
0,弹出清理函数,不执行清理函数。
当线程执行以下动作时会调用清理函数:
- 调用pthread_exit退出进程。
- 响应其他线程的取消请求。
- 用非零execute调用pthread_cleanup_pop
无论哪种情况pthread_cleanup_pop都将删除上一次pthread_cleanup_push调用组测的清理处理函数。
1.9.1 调用pthread_exit函数时,系统自动调用线程清理函数
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void mycleanup(void* arg)
{
printf("cleann up ptr = %s\n", (char*)arg);
free((char*)arg);
}
void* pthread_fun(void* arg)
{
char* ptr = NULL;
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(mycleanup, (void*)(ptr));
memset(ptr, 0, 100);
strcpy(ptr, "memory from malloc");
sleep(3);
printf("before exit\n");
pthread_exit(NULL);
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthread_fun, NULL);
pthread_join(thread, NULL);
printf("process is dying\n");
return 0;
}
执行截图:
1.9.2 线程被取消时,系统自动调用线程清理函数
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void mycleanup(void* arg)
{
printf("cleann up ptr = %s\n", (char*)arg);
free((char*)arg);
}
void* pthread_fun(void* arg)
{
char* ptr = NULL;
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(mycleanup, (void*)(ptr));
memset(ptr, 0, 100);
strcpy(ptr, "memory from malloc");
sleep(10);
// pthread_exit(NULL);
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthread_fun, NULL);
sleep(5);
printf("before cancel\n");
pthread_cancel(thread);
pthread_join(thread, NULL);
printf("process is dying\n");
return 0;
}
执行截图:
1.9.3 调用pthread_cleanup_pop函数时,系统自动调用线程清理函数
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void cleanup_func1(void* arg)
{
printf("in clanup func1\n");
printf("clean up ptr = %s\n", (char*)arg);
free((char*)arg);
}
void cleanup_func2(void* arg)
{
printf("in cleanup func2\n");
}
void* pthread_fun(void* arg)
{
char* ptr = NULL;
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup_func1, (void*)(ptr));
pthread_cleanup_push(cleanup_func2, (void*)(ptr));
memset(ptr, 0, 100);
strcpy(ptr, "memory from malloc");
sleep(3);
printf("before pop\n");
pthread_cleanup_pop(1);
printf("before pop\n");
pthread_cleanup_pop(1);
}
int main()
{
pthread_t thread;
pthread_create(&thread, NULL, pthread_fun, NULL);
pthread_join(thread, NULL);
printf("process is dying\n");
return 0;
}
执行截图:
总结:
在本篇博客中,我们详细介绍了线程编程中的相关函数。通过了解和掌握这些函数,我们能够更灵活地创建、管理和同步线程,实现并发编程和多任务处理。这些函数包括线程的创建、启动、等待和终止等,是编写多线程应用程序的关键工具。
掌握线程函数的使用方法可以帮助我们构建高效、可靠的多线程应用程序,并充分利用计算资源来提升性能。这些函数的灵活应用使我们能够实现线程间的协调和通信,避免竞态条件和死锁等并发编程中常见的问题。