1. pthread_create
功能
创建一个新的线程。
原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数
thread
: 指向pthread_t
类型变量的指针,用于存储新创建线程的标识符。attr
: 线程属性,通常为NULL表示默认属性。如果需要指定线程属性,可以使用pthread_attr_t
结构。start_routine
: 新线程的入口函数,即新线程将从这个函数开始执行。线程开始执行的函数类型为void *(*start_routine)(void *)
,它接受一个void *
参数并返回一个void *
值。arg
: 传递给新线程入口函数的参数。
返回值
- 成功:返回0。
- 失败:返回错误代码。
pthread_t t1;
int param = 100;
int ret = pthread_create(&t1, NULL, func1, (void *)¶m);
if (ret != 0) {printf("Error creating thread: %d\n", ret);
}
2. pthread_self
功能:获取调用线程的线程标识符。
原型
pthread_t pthread_self(void);
参数:无。
返回值:返回调用线程的线程标识符。
用途: 常用于调试、日志记录以及需要区分线程的情景。
pthread_t tid = pthread_self();
printf("Current thread ID: %ld\n", (unsigned long)tid);
3. pthread_join
功能:等待指定的线程终止。pthread_join
会阻塞调用它的线程,直到指定的线程结束。这对于确保所有线程在主线程退出之前完成工作非常有用。
原型
int pthread_join(pthread_t thread, void **retval);
参数
thread
: 要等待的线程的标识符。retval
: 存储线程返回值的指针。如果不需要获取线程的返回值,可以传递NULL。如果需要获取线程的返回值,retval
参数不能为NULL,应该传递一个指向指针的指针。
返回值
- 成功:返回0。
- 失败:返回错误代码。
示例
pthread_t t1;
// 创建线程 t1
pthread_create(&t1, NULL, func1, (void *)¶m);// 等待线程 t1 结束
pthread_join(t1, NULL);
4. pthread_exit
功能:终止调用
它的线程。该函数用于显式地终止线程,通常用于线程需要提前结束的情况。
自动清理: 调用pthread_exit
的线程会自动释放分配给它的资源。
原型
void pthread_exit(void *retval);
参数
retval
: 线程的返回值,传递给pthread_join
的retval
参数。
返回值
无,因为此函数不会返回到调用线程。
示例
void *func1(void *arg) {printf("Thread is exiting.\n");pthread_exit(NULL);
}
示例代码
结合上述函数,这里做个演示
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void * func1(void *arg)
{//关于为什么要使用static,请看下面的解释static char *p = "t1 is run"; //为什么转换成unsigned long,请看下面的解释printf("func1: %ld\n", (unsigned long)pthread_self());//退出当前线程,并返回一个指针给pthread_joinpthread_exit((void *)p);
}int main()
{//定义一个线程标识符pthread_t t1;int data = 100;char *pret = NULL;int ret = pthread_create(&t1, NULL, func1, (void *)&data);if(ret == 0){printf("successfully creat a pthread!\n");}else{printf("faild to creat a pthread\n");}printf("I am main: %ld\n", (unsigned long)pthread_self());pthread_join(t1, (void **)&pret);printf("main: t1 quit: %s\n", pret);return 0;
}
问题1:为什么要使用static静态变量?
非静态变量的行为
考虑一个没有使用 static
的情况:
#include <stdio.h>void *func1(void *arg) {char p[] = "t1 is run"; // 非静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result); // 未定义行为,可能导致错误return 0;
}
在这个例子中:
char p[]
是一个局部数组变量,存储在栈中。- 当
func1
返回时,p
指向的内存可能已无效,因为函数返回后栈帧被销毁。 - 访问返回的指针会导致未定义行为,可能会崩溃或输出垃圾值。
C 语言标准中规定,对于局部变量(自动变量),当函数返回时,它们的内存会被释放。如果你返回一个指向局部变量的指针,这个指针会指向无效内存。虽然在某些情况下,内存内容可能未被立即覆盖或销毁,因此可以“侥幸”地正确打印,但这种做法是不安全的。
使用静态变量
现在使用 static
关键字:
#include <stdio.h>void *func1(void *arg) {static char p[] = "t1 is run"; // 静态局部变量return (void *)p;
}int main() {void *result = func1(NULL);printf("Result: %s\n", (char *)result); // 正常输出 "t1 is run"return 0;
}
在这个例子中:
static char p[]
是一个静态变量,存储在静态数据区而不是栈中。- 变量
p
在第一次调用时初始化,并在程序的整个运行期间保留其值和内存地址。 - 当
func1
返回时,返回的指针p
始终有效,可以安全地访问。
关键点总结
- 持久性:静态变量在整个程序运行期间存在,不会因函数返回而销毁。
- 唯一初始化:静态变量只在第一次声明时初始化,后续调用不会重新初始化。
- 作用域限制:静态局部变量的作用域限制在函数内部,无法被外部访问。
使用静态变量的场景
静态变量适用于以下场景:
- 返回局部数据的地址:当函数需要返回局部数据的地址时,可以使用静态变量确保数据在函数返回后依然有效。
- 保持函数调用间的状态:当函数需要在多次调用之间保持某些状态时,使用静态变量可以实现状态的持久化。
问题2:为什么将pthread_self()的返回值转换为unsigned long类型?
- 不透明类型:
pthread_t
是一个不透明类型,其实现依赖于具体的操作系统和线程库。在某些系统上,pthread_t
可能是一个整数类型,在其他系统上可能是一个指针类型。 - 类型大小不同:不同系统上,
pthread_t
的大小可能不同(例如,32位系统和64位系统上类型大小可能不同)。
使用 unsigned long
的理由
- 通用格式化:
unsigned long
是一种标准的整数类型,在C语言中占用固定的字节数(通常是32位或64位)。这使得它在大多数系统上都可以以一种一致的方式打印。 - 格式化支持:
printf
函数支持%lu
格式说明符来打印unsigned long
类型,这在C语言标准库中是通用的。 - 避免类型问题:将
pthread_t
转换为unsigned long
可以避免由于不同系统上pthread_t
类型不同带来的编译和运行时问题。
问题3:关于线程函数
void *thread_func(void *arg) {}pthread_create(&t1, NULL, thread_func, (void *)¶m);
-
线程函数:
thread_func
是符合void *(*)(void *)
类型的函数,它接受一个void *
类型的参数,并返回一个void *
类型的值。 -
创建线程:
pthread_create(&t1, NULL, thread_func, (void *)¶m);
- 第一个参数
&t1
是指向线程标识符的指针。 - 第二个参数
NULL
表示使用默认线程属性。 - 第三个参数
thread_func
是线程函数的指针。 - 第四个参数
(void *)¶m
是传递给线程函数的参数,强制转换为void *
类型。为什么要强制转换呢,接着往下看问题4,就明白了。
- 第一个参数
问题4:pthread_create的
第三个参数类型void *(*start_routine)(void *),怎么去理解?
我们可以拆分来理解:
-
void *
: 表示函数的返回类型是一个void *
指针。线程函数可以返回任意类型的数据,通过指针的方式返回。 -
(*start_routine)
: 表示这是一个指向函数的指针,指向的函数名为start_routine
。 -
(void *)
: 表示这个函数接受一个void *
类型的参数。这个参数可以是任意类型的数据,通过指针的方式传递。
void *(*start_routine)(void *)
表示一个指向函数的指针(*start_routine)
,这个函数接受一个void *
类型的参数(void *)
,并返回一个void *
类型的值。也就是说对应的函数要为指针函数。
问题5:关于多次使用到void *
void *
类型
void *
是一种通用指针类型,可以指向任意类型的数据。它不携带任何类型信息,这意味着你可以将任何类型的数据指针转换为 void *
,并且可以从 void *
类型转换回原始类型的指针。
为什么使用 void *
在需要传递不确定类型的数据时,void *
非常有用。例如,在多线程编程中,你可能希望线程函数能够处理不同类型的参数,而 void *
允许你传递任意类型的数据指针。
问题6:pthread_join的第二个参数
pthread_join
内部如何工作
在 pthread_join
的实现中,内部逻辑大概如下:
- 等待线程结束:主线程会阻塞等待子线程
t1
结束。 - 获取线程的返回值:当线程调用
pthread_exit((void *)p)
退出时,返回的指针p
会被传递回pthread_join
。 - 存储返回值:
pthread_join
将返回的指针p
存储在retval
所指向的地址中。
在 pthread_join
调用中传递 &pret
的目的是让线程库在 pthread_join
内部通过这个指针来修改 pret
的值(意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值),使 pret
指向线程函数返回的地址。意思是把pret的地址穿给线程库,线程库知道了pret在哪,然后才能找到pret去修改pret的值。