一、引言
多进程在使用时有很多不方便,比如:
- 创建进程开销较大
- 进程间数据交换需要特殊的 IPC 技术
- 进程切换需要 CPU 频繁的“上下文切换”,造成极大的开销
因此衍生出了线程技术,线程和进程一样都有一个唯一标识符。
在 Linux 上使用线程,在编译链接时需要对线程库进行链接:g++ demo.cpp -o demo -lpthread
。
线程是系统调度的最小单位(协程的创建和收束在用户态完成,而线程的创建、调度和收束都由系统完成),支持并发运行,并发的线程数由硬件设备决定,通常和 CPU 核心数相同。
二、线程使用
1.2 线程状态
Linux 线程分为两种状态:
- 可结合状态(Joinable):默认状态,线程终止后需要通过
pthread_join
显式回收其资源(如栈内存、线程描述符等)。 - 分离状态(Detached):线程终止时,系统自动回收资源,无需其他线程调用
pthread_join
。线程可通过pthread_detach
或创建时设置属性实现分离。线程一旦被分离无法恢复可结合状态。
当 main
线程结束时,无论是分离线程还是可结合线程(如果忘记使用 pthread_join
回收)都会被强制回收,即便子线程处于死循环状态。
1.3 线程常用函数
线程创建
1
2
3
#include <pthread.h>
int pthread_create(pthread_t * restrict thread, const pthread_attr_t * restrict attr, void * (* start_routine)(void *), void * restrict arg);
thread
:输出参数,存储新线程的标识符。
attr
:线程属性(如栈大小、调度策略),默认 NULL
表示使用默认属性。
start_routine
:线程入口函数(需返回 void*
)。
arg
:传递给入口函数的参数(void*
类型)。
restrict
:表示该指针是访问其所指向内存区域的唯一方式,通过 restrict
修饰的指针,编译器会假设其指向的内存不会被其他指针访问或修改,从而省略冗余的内存访问检查,直接基于指针的当前值进行优化
返回值:成功返回 0,失败返回错误码。
线程终止
1
void pthread_exit(void *retval);
在线程内部调用,retval
为线程退出时的返回值,可通过 pthread_join
获取。
线程等待
1
int pthread_join(pthread_t thread, void **retval);
等待线程 TID
为 thread
的线程结束,并传出 retval
参数。该函数为阻塞函数,直到目标线程终止并收回资源。
线程分离
1
int pthread_detach(pthread_t thread);
将线程 thread
设为分离状态,线程终止后自动释放资源(无需 pthread_join
)。
结构体
1
2
3
4
5
typedef union {
char __size[__SIZEOF_PTHREAD_ATTR_T]; // 内部存储空间
long int __align; // 内存对齐
} pthread_attr_t;
typedef unsigned long int pthread_t; // 常见实现(Linux)
在使用线程时往往要考虑临界资源,很多标准函数都是线程安全的函数。有些不是线程安全的函数,往往也会提供同一功能的线程安全函数,如:
struct hostent * gethostbyname(const char * hostname);
不是线程安全函数,但是struct hostent * gethostbyname_r(const char * name, struct hostent * result, char * buffer, intbuflen, int * h_errnop);
是线程安全的函数。并且可以在声明头文件前定义_REENTRANT
宏,直接使用gethostbyname_r
函数取代gethostbyname
函数。
四、其他函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 非阻塞尝试加锁
// 条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒单个等待线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程
// 初始化与销毁
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
// 设置分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
// 设置调度策略与优先级
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy); // 如 SCHED_FIFO
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
// 获取线程 ID
pthread_t pthread_self(void);
// 线程特定数据
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_setspecific(pthread_key_t key, const void *value);
void *pthread_getspecific(pthread_key_t key);
五、示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <pthread.h>
#include <stdio.h>
void* thread_func(void *arg) {
printf("Thread ID: %lu\n", pthread_self());
return NULL;
}
int main() {
pthread_t tid;
pthread_attr_t attr;
// 初始化属性
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// 创建线程
if (pthread_create(&tid, &attr, thread_func, NULL) != 0) {
perror("pthread_create failed");
return 1;
}
// 等待线程结束
pthread_join(tid, NULL);
pthread_attr_destroy(&attr);
return 0;
}