目录
- 一、基本知识点
- 二、线程的编译
- 三、 线程相关函数
- 1. 线程的创建
- (1)整型的传入与接收
- (2)浮点数的传入与接收
- (3)字符串的传入与接收
- (4)结构体的传入与接收
- 2. 线程的退出
- 3. 线程的等待
- 补充
- (1)返回整型
- (2)返回浮点数
- (3)返回字符串
- (4)返回结构体
- 四、综合举例
一、基本知识点
-
定义
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。每一个进程都至少包含一个main线程。
相比于进程而言,线程更加轻量级,因为它们共享了进程的地址空间以及其他资源,所以线程之间的切换和通信会更加高效。一个进程可以包含多个线程,这些线程可以并发执行,各自独立完成一些特定的任务,或者共同完成一个复杂的任务。进程里每个线程执行的顺序不一定。主线程结束,则所有线程结束。
-
共享资源保护
由于某一线程与同属一个进程的其他的线程共享进程的资源,如内存空间、文件描述符和其他一些进程相关的属性。所以在多线程编程中,通常会涉及到共享资源保护操作,如互斥锁等以确保线程之间的协调运行,避免资源竞争和数据不一致的问题。即每次对共享资源进行操作时,只能有一个线程操作,其他线程必须等待操作完毕后,才能对其继续操作。
二、线程的编译
Linux 的线程是通过用户级的函数库实现的,一般采用 pthread
线程库实现线程的访问和控制。它用第 3 方 posix 标准的 pthread,具有良好的可移植性。在使用了线程的代码编译的时候要在后面加上 -lpthread
。
例如:gcc test.c -o test -lpthread
三、 线程相关函数
头文件:#include <pthread.h>
1. 线程的创建
int pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
//pthread_t* thread :线程的句柄,用于区分是哪个线程。
//pthread_attr_t * attr :线程的属性,通常为NULL。
//void *(*start_routine)(void *) :线程所执行的函数,该函数形参和返回值都要为void *。
// void * arg :该值用于传递第三个参数函数的形参,如果不传递可以为NULL。
举例:
(1)整型的传入与接收
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{
int *num=(int *)arg;
while(1)
{
sleep(1);
printf("我是子线程,传入参数为%d\n",*num);
}
}
int main()
{
int num =100;
pthread_t thread; //定义线程句柄
pthread_create(&thread, NULL, task, (void *)&num); //创建线程,并绑定线程函数。
while(1){
sleep(1);
printf("我是主线程\n");
}
}
(2)浮点数的传入与接收
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{
float*num=(float *)arg;
while(1)
{
sleep(1);
printf("我是子线程,传入参数为%f\n",*num);
}
}
int main()
{
float num =10.10;
pthread_t thread; //定义线程句柄
pthread_create(&thread, NULL, task, (void *)&num); //创建线程,并绑定线程函数。
while(1){
sleep(1);
printf("我是主线程\n");
}
}
(3)字符串的传入与接收
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task(void *arg)
{
char *num=(char *)arg;
while(1)
{
sleep(1);
printf("我是子线程,传入参数为%s\n", num);
}
}
int main()
{
char *string="i love you!";
pthread_t thread; //定义线程句柄
pthread_create(&thread, NULL, task, (void *)string); //创建线程,并绑定线程函数。
while(1){
sleep(1);
printf("我是主线程\n");
}
}
(4)结构体的传入与接收
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
struct people{
char *name;
int age;
};
void *task(void *arg)
{
struct people *student=(struct people*)arg;
while(1)
{
sleep(1);
printf("我是子线程,传入参数名字:%s,年龄:%d\n", student->name,student->age);
}
}
int main()
{
struct people *student;
pthread_t thread; //定义线程句柄
//将结构体赋值
student->age=10;
strcpy(student->name, "John");
pthread_create(&thread, NULL, task, (void *)student); //创建线程,并绑定线程函数。
while(1){
sleep(1);
printf("我是主线程\n");
}
}
2. 线程的退出
函数 pthread_exit 表示线程的退出。其传入的的参数可以被其它线程用 pthread_join
等待函数进行捕获。例如: pthread_exit( (void*)3 );
表示线程退出,并将 3作为返回值被后面的 pthread_join 函数捕获。该函数的作用就是可以将该线程的计算结果传递给创建它的主线程或者其他线程。
注意1:在使用线程函数时,不能随意使用 exit 退出函数进行出错处理,由于 exit 的作用是使调用进程终止,往往一个进程包括了多个线程,所以在线程中通常使用 pthread_exit 函数来代替进程中的退出函数 exit。
注意2:如果线程退出的返回值是在线程内部定义的局部变量的话,记得加static
修饰,增加变量的生命周期直至进程结束而结束。不然线程退出后,该片内存地址会立刻被销毁,此时返回的空间无效。或者将返回值修改为全局变量。
void pthread_exit(void *retval);
//void *retval:作为线程退出时的值。如果不需要捕获该值,可以传入NULL。
3. 线程的等待
当父线程结束的时候,如果没有 pthread_join
函数等待子线程执行的话,父线程会退出,而主线程的退出会导致进程的退出,故子线程也会退出。
由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以通过 wait()函数系统调用来同步终止并释放资源一样,线程之间也有类似的机制,那就是 pthread_join 函数。这个函数是一个线程阻塞函数,调用这函数的线程将一直等待直到被等待的线程结束为止,当函数返回时,被等待线程的资源被回收。
int pthread_join(pthread_t pthid, void **thread_return);
//pthread_t pthid :线程句柄。
//void **thread_return : 用于接收线程退出的返回值。如果没有需要接收的填NULL。
上述函数第二个参数为二级指针,所以我们可以定义一级指针再取地址来获取该值,然后再进行强制类型转换为(void **)。
举例:
思考:上述例程中为什么要用static来修饰变量呢?
答:这是因为当线程退出时,其在内部定义的变量都会被销毁,可以将变量设为全局变量或者使用static来修饰变量,增加其生命周期,直至进程结束,才会被销毁。不然返回的地址将是一个已经被销毁的地址,无法使用。
补充
一般线程的退出和线程的等待是一起使用的。这里我们使用几种参数类型进行举例。
(1)返回整型
//退出:
static int a=10;
pthread_exit((void *) &a);
//-------------------------------
//等待接收:
int *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%d\n", *thread_result);
(2)返回浮点数
//退出:
static float f=10.12;
pthread_exit((void *) &f);
//-------------------------------
//等待接收:
float *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%f\n", *thread_result);
(3)返回字符串
//退出:
static char *string="i love you!";
pthread_exit((void *)string);
//-------------------------------
//等待接收:
char *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("子线程的返回值为:%s\n", thread_result);
(4)返回结构体
typedef struct
{
char *name; //定义指针,后续使用时要为其开辟内存空间! 如果不想开辟空间,则使用数组。
int age;
}people;
//退出:
people *student = (people *)malloc(sizeof(people)); //给结构体开辟空间,然后赋值。
student->name=(char *)malloc(sizeof(char *)); //为结构体里定义的字符指针开辟内存空间。
//给结构体赋值
strcpy(student->name,"john");
student->age=12;
pthread_exit((void *)student);
//等待接收
student *thread_result;
pthread_join(thread,(void **)&thread_result);
printf("名字为:%s,年龄为:%d\n", thread_result->name,thread_result->age);
四、综合举例
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *task1(void * arg) //线程1函数
{
float *num =(float *)arg;
while(1)
{
sleep(1);
printf("这是子线程1,传入的值为%f\n",*num);
}
}
void *task2(void * arg) //线程2函数
{
int *num =(int *)arg;
static char *string="pthread2_end";
while(1)
{
sleep(1);
printf("这是子线程2,传入的值为%d\n",*num);
(*num)--;
if(*num <0) break;
}
pthread_exit((void *)string);
}
int main()
{
float pthread1_arg=11.2; //传入线程函数1的参数
int pthread2_arg=3; //传入线程函数2的参数
char *string;
int ret;
pthread_t thread1,thread2;//线程句柄
//创建线程1
ret=pthread_create(&thread1, NULL, task1, (void *) &pthread1_arg);
if(ret<0)
{
perror("pthread1_create error!\n");
return -1;
}
//创建线程2
ret=pthread_create(&thread2, NULL, task2, (void *) &pthread2_arg);
if(ret<0)
{
perror("pthread2_create error!\n");
return -1;
}
pthread_join(thread2, (void **)&string);
printf("%s\n",string);
return 0;
}