Linux线程
前言
- 一个程序内的多个执行路线
- 一个进程内部的控制序列
- 新的线程拥有自己的栈,与它的创建者共享全局变量,文件描述符,信号句柄等资源
- 线程执行开销小,但不利于资源的管理和保护
线程函数
pthread_create函数
- 创建一个新的线程
接口代码
#include <pthread.h>
int pthread_create(pthread_t* thread,pthread_attr_t* attr,void*(*start_routine)(void *),void* arg);
参数解释
- thread: 线程标识符
- attr: 用于指定线程属性,可以使用
NULL
表示使用默认属性.常用线程属性包括堆栈大小,调度策略 - start_routine: 线程启动函数指针,就是线程启动函数
- arg: 线程启动函数参数
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码,并且thread不会被修改。常见的错误码包括:
EAGAIN
:系统资源不足,无法创建新的线程。EINVAL
:attr
指向的属性无效。ENOMEM
:内存不足,无法分配所需的资源。
实例代码
// 见下方汇总
pthread_exit
- 线程终止函数
- 终止线程并不会释放互斥锁
接口代码
#include <pthread.h>
void pthread_exit(void* retval);
参数解释
- retval: 返回值,可以为NULL
实例代码
// 见下方汇总
pthread_join
- 线程等待函数,类似于
wait
函数
接口代码
#include <pthread.h>
int pthread_join(pthread_t th,void** thread_return);
参数解释
- th: 线程描述符
- thread_return: 所等待的线程的返回值
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。常见的错误码包括:
ESRCH
:指定的线程 ID 不存在。EINVAL
:线程 ID 无效。EDEADLK
:检测到死锁情况,通常是因为尝试对已经加入或分离的线程再次调用pthread_join
。
实例代码
// 见下方汇总
练习–线程创建,关闭
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 线程启动函数
void *thread_function(void *arg)
{
// 接受参数
int *val = (int *)arg;
printf("线程ID: %lu,参数: %d\n", pthread_self(), *val);
*val = 999;
// 线程退出
pthread_exit((void *)(__intptr_t)*val);
// pthread_exit(NULL)
// return (void *)(__intptr_t)*val;
}
int main()
{
pthread_t thread; // 线程标识符
int val = 100; // 传递给线程的参数
// 创建新线程
if (pthread_create(&thread, NULL, thread_function, &val) != 0)
{
perror("线程创建失败");
exit(1);
}
// 主线程继续执行
printf("主线程ID: %lu\n", pthread_self());
// 等待线程结束
void *threadRes;
if (pthread_join(thread, &threadRes) != 0)
{
perror("线程异常");
exit(1);
}
// 打印线程返回值
if (threadRes != NULL)
{
int res = (__intptr_t)threadRes;
printf("线程返回值为: %d\n", res);
}
else
printf("线程没有返回值\n");
exit(0);
}
线程同步机制
- 信号量: 仅用于线程间同步
- 互斥量: 信号量的另一种应用,线程同步机制的一种,某个进程先获得资源后,后访问资源的线程会被阻塞
信号量
sem_init
- 信号量初始化
接口代码
#include <semaphore.h>
int sem_init(sem_t* sem,int pshared,unsigned int value);
参数解释
sem
:- 类型:
sem_t *
- 描述:这是一个指向
sem_t
类型的指针,表示要初始化的信号量对象。
- 类型:
pshared
:- 类型:
int
- 描述:这个参数指定信号量的共享范围。
- 如果
pshared
为 0,信号量将在单个进程内共享。 - 如果
pshared
不为 0,信号量可以在多个进程之间共享。在这种情况下,sem
指向的内存必须位于共享内存段中(例如通过shm_open
和mmap
创建的共享内存)。
- 如果
- 类型:
value
:- 类型:
unsigned int
- 描述:这是信号量的初始值。信号量的值通常表示可用资源的数量。初始值可以是任何非负整数。
- 类型:
返回值
- 成功:返回 0。
- 失败:返回 -1,并设置
errno
以指示错误类型。常见的错误码包括:EINVAL
:value
超出了信号量的最大值。EAGAIN
:系统资源不足,无法初始化信号量。
sem_wait/sem_post
sem_wait
: 信号量减一,当信号量减为0时,就阻塞线程sem_post
: 信号量加一
接口代码
#include <semaphore.h>
int sem_wait(sem_t* sem);
int sem_post(sem_t* sem);
参数解释
- sem: 信号量
返回值
- 成功:返回0
- 不成功:返回-1
sem_destory
- 信号量清理函数
sem_destory
接口代码
#include <semaphore.h>
int sem_destory(sen_t* sem);
参数解释
- sem: 信号量
返回值
- 成功:返回0
- 不成功:返回-1
练习–信号量访问临界区资源
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
// 信号量
sem_t sem;
// 共享资源
int shared_num = 0;
// 线程启动函数
void *thread(void *arg)
{
int thread_id = *(int *)arg;
// 等待信号量
if (sem_wait(&sem) == -1)
{
perror("临界区正在被访问");
pthread_exit(NULL);
}
// 进入临界区
printf("线程 ID: %d, 进入临界区\n", thread_id);
// 模拟睡眠
sleep(2);
// 访问共享资源
shared_num++;
printf("线程 ID: %d, 修改共享资源: %d\n", thread_id, shared_num);
// 退出临界区
printf("线程 ID: %d, 退出临界区\n", thread_id);
// 释放信号量
if (sem_post(&sem) == -1)
{
perror("释放失败");
pthread_exit(NULL);
}
return NULL;
}
int main()
{
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
// 初始化信号量
if (sem_init(&sem, 0, 1) == -1)
{
perror("信号量初始化失败");
exit(-1);
}
// 创建线程
if (pthread_create(&thread1, NULL, thread, &id1) == -1)
{
perror("线程一创建失败");
exit(1);
}
if (pthread_create(&thread2, NULL, thread, &id2) == -1)
{
perror("线程二创建失败");
exit(1);
}
// 主线程继续执行
printf("主线程 ID: %lu\n", pthread_self());
// 等待线程结束
if (pthread_join(thread1, NULL) == -1)
{
perror("线程一异常");
exit(1);
}
if (pthread_join(thread2, NULL) == -1)
{
perror("线程二失败");
exit(1);
}
// 销毁信号量
if (sem_destroy(&sem) != 0)
{
perror("信号量销毁失败");
exit(1);
}
exit(0);
}
互斥锁
- 基于信号量,但比信号量要求更严格,同一时间只允许一个进程访问临界区资源
pthread_mutex_init
- 初始化互斥量锁
接口代码
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t *mutexattr);
参数解释
- mutex: 互斥锁对象
- mutexattr: 指定互斥锁属性,默认的话写
NULL
- 类型:互斥锁的类型,例如普通互斥锁、递归互斥锁等。
- 协议:互斥锁的优先级继承或优先级天花板协议。
- 进程共享:互斥锁是否可以在多个进程之间共享。
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。常见的错误码包括:
EINVAL
:mutex
或mutexattr
指向无效的地址。ENOMEM
:系统资源不足,无法初始化互斥锁。
pthread_mutex_lock\pthread_mutex_unlock
pthread_mutex_lock
: 加锁,别的进程阻塞pthread_mutex_unlock
: 解锁
接口代码
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数解释
- mutex: 互斥锁对象
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。
pthread_mutex_destory
- 清理互斥锁
接口代码
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数解释
- mutex: 互斥锁对象
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。
练习–互斥访问临界区资源
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
// 定义互斥锁
pthread_mutex_t mutex;
// 共享资源
int shareSource = 0;
// 线程启动函数
void *thread(void *arg)
{
int threadID = *(int *)arg;
// 加锁
if (pthread_mutex_lock(&mutex) == -1)
{
perror("临界区正在使用");
pthread_exit(NULL);
}
// 进入临界区
printf("线程 ID: %d, 进入临界区\n", threadID);
// 模拟一些工作
sleep(2);
// 访问共享资源
shareSource++;
printf("线程 ID: %d, 修改共享资源: %d\n", threadID, shareSource);
// 退出临界区
printf("线程 ID: %d, 退出临界区\n", threadID);
// 解锁
if (pthread_mutex_unlock(&mutex) == -1)
{
perror("解锁失败");
pthread_exit(NULL);
}
return NULL;
}
int main()
{
pthread_t thread1, thread2;
int id1 = 1, id2 = 2;
// 初始化互斥锁
if (pthread_mutex_init(&mutex, NULL) == -1)
{
perror("初始化互斥锁失败");
exit(1);
}
// 创建线程
if (pthread_create(&thread1, NULL, thread, &id1) == -1)
{
perror("线程一创建失败");
exit(1);
}
if (pthread_create(&thread2, NULL, thread, &id2) == -1)
{
perror("线程二创建失败");
exit(1);
}
// 主线程继续执行
printf("主线程 ID: %lu\n", pthread_self());
// 等待线程结束
if (pthread_join(thread1, NULL) == -1)
{
perror("线程一异常");
exit(1);
}
if (pthread_join(thread2, NULL) == -1)
{
perror("线程二失败");
exit(1);
}
// 销毁互斥锁
if (pthread_mutex_destroy(&mutex) == -1)
{
perror("互斥锁销毁失败");
exit(1);
}
exit(0);
}
线程控制
- 可以独自设置线程的配置,例如
- 堆栈大小:设置线程的堆栈大小。
- 堆栈地址:指定线程堆栈的起始地址。
- 分离状态:指定线程是否是分离的(detached)。分离的线程在终止时会自动释放资源,不需要调用
pthread_join
。- 调度策略:设置线程的调度策略(如 SCHED_FIFO、SCHED_RR 或 SCHED_OTHER)。
- 调度优先级:设置线程的调度优先级。
- 继承性:指定新线程是否继承创建它的线程的信号掩码
pthread_attr_init
- 线程属性初始化
接口代码
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
参数解释
-
attr
:-
类型:
pthread_attr_t *
-
描述:这是一个指向
pthread_attr_t
类型的指针,表示要初始化的线程属性对象。
-
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。常见的错误码包括:
EINVAL
:attr
指向无效的地址。ENOMEM
:系统资源不足,无法初始化线程属性对象。
pthread_attr_setdetachstate
- 线程属性修改函数
接口代码
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
参数解释
- attr:
- 堆栈大小:设置线程的堆栈大小。
- 堆栈地址:指定线程堆栈的起始地址。
- 分离状态:指定线程是否是分离的(detached)。分离的线程在终止时会自动释放资源,不需要调用
pthread_join
。 - 调度策略:设置线程的调度策略(如 SCHED_FIFO、SCHED_RR 或 SCHED_OTHER)。
- 调度优先级:设置线程的调度优先级。
- 继承性:指定新线程是否继承创建它的线程的信号掩码。
- detachstate:这是要设置的分离状态。可以取以下两个值之一:
PTHREAD_CREATE_JOINABLE
:线程在终止时保持可连接状态。这意味着其他线程可以通过调用pthread_join
来等待该线程结束,并获取其退出状态。PTHREAD_CREATE_DETACHED
:线程在终止时会自动释放所有资源,并且不能被其他线程通过pthread_join
等待。这种状态下,线程一旦终止,其资源会被立即回收。
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。常见的错误码包括:
EINVAL
:attr
指向无效的地址,或者detachstate
的值不是PTHREAD_CREATE_JOINABLE
或PTHREAD_CREATE_DETACHED
。
pthread_attr_destroy
- 线程属性销毁
接口代码
#include <pthread.h>
int pthread_attr_destroy(pthread_attr_t *attr);
参数解释
- attr: 定义线程属性对象
返回值
- 成功:返回 0。
- 失败:返回一个非零错误码。
练习–线程属性设置
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h> // for sleep
// 线程启动函数
void *thread_function(void *arg)
{
int thread_id = *(int *)arg;
// 打印线程 ID
printf("线程 ID: %d, 正在运行\n", thread_id);
// 模拟一些工作
sleep(2);
return NULL;
}
int main()
{
pthread_t thread;
int id = 1;
pthread_attr_t attr; // 定义线程属性对象
// 初始化线程属性对象
if (pthread_attr_init(&attr) == -1)
{
perror("初始化失败");
exit(1);
}
// 设置线程为分离状态
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0)
{
perror("线程分离设置失败");
exit(1);
}
// 创建线程
if (pthread_create(&thread, &attr, thread_function, &id) == -1)
{
perror("线程创建失败");
exit(1);
}
// 主线程继续执行
printf("主线程 ID: %lu\n", pthread_self());
// 由于线程是分离状态,所以不需要调用 pthread_join
// 销毁线程属性对象
if (pthread_attr_destroy(&attr) == -1)
{
perror("线程对象销毁失败");
exit(1);
}
// 主线程可以继续执行其他任务
sleep(3); // 等待一段时间,确保子线程完成
exit(0);
}