线程概念
线程是操作系统中一种基本的执行单元,是程序的最小调度单位。一个程序可以包含多个线程,每个线程代表一个独立的执行路径,使得程序可以并发地处理多个任务。
线程的基本概念
线程与进程的区别:
进程是资源分配的单位,每个进程拥有独立的内存空间、文件描述符、全局变量等资源。进程之间隔离较好,但创建和切换成本较高。
线程是调度的基本单位,同一进程中的线程共享进程的资源(如地址空间、文件描述符等),但拥有独立的栈、寄存器和程序计数器。线程间切换开销较低,适合并发处理。
线程的组成部分: 每个线程包含以下关键部分:
栈:用于存储线程的局部变量和函数调用栈。
程序计数器(PC):记录线程的当前执行位置。
寄存器:保存线程的运行状态。
线程本地存储(TLS):线程间独立的数据存储。
线程的优点:
并发性:在多线程环境下,程序可以同时执行多个任务,提升执行效率。
资源共享:同一进程内的线程可以直接访问共享内存、全局变量等资源,避免了进程间通信的开销。
低开销:线程的创建和上下文切换开销比进程低,使多线程适合 I/O 密集型和高并发任务。
线程共享的资源
线程共享的资源
地址空间:包括代码段、数据段、堆空间等,使线程间可以直接访问彼此的全局变量和堆内存。
文件描述符表:所有线程共享文件和网络连接,能共同读写相同的文件。
信号处理:大多数信号处理器是共享的,一些信号会影响整个进程的所有线程。
当前工作目录和环境变量:线程共享进程的当前工作目录和环境变量,任何改动会影响所有线程。
用户 ID 和组 ID:所有线程共享进程的用户 ID(UID)和组 ID(GID)。
线程独立的资源
栈:每个线程有独立的栈空间,用于存储各自的函数调用和局部变量,避免相互干扰。
寄存器:每个线程独立拥有寄存器状态(包括程序计数器和栈指针等),确保在线程切换时能正确恢复。
线程本地存储(TLS):每个线程可以有独立的线程局部变量(Thread-local variables),即便变量名称相同也不冲突。
信号掩码:每个线程有独立的信号掩码,控制各线程可以接受或阻塞哪些信号。
优先级:每个线程可以设置自己的优先级,影响调度顺序。
在多线程环境中,errno 不是线程共享的,而是 线程局部变量(Thread-Local Storage, TLS)。这意味着每个线程都有自己的 errno 值,彼此独立,互不干扰。
线程创建 – pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread, const
pthread_attr_t *attr, void *(*routine)(void *), void *arg);
成功返回0,失败时返回错误码
thread 线程对象
attr 线程属性,NULL代表默认属性
routine 线程执行的函数
arg 传递给routine的参数 ,参数是void * ,注意传递参数格式,
线程结束 – pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
结束当前线程
retval可被其他线程通过pthread_join获取
线程私有资源被释放
线程查看tid函数
pthread_t pthread_self(void) 查看自己的TID
#include <pthread.h>
pthread_t pthread_self(void);
线程回收 – pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
对于一个默认属性的线程 A 来说,线程占用的资源并不会因为执行结束而得到释放
成功返回0,失败时返回错误码
thread 要回收的线程对象
调用线程阻塞直到thread结束
*retval 接收线程thread的返回值
线程分离pthead_detach
int pthread_detach(pthread_t thread); 成功:0;失败:错误号
指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程)
pthread_attr_t attr; /通过线程属性来设置游离态(分离态)/
设置线程属性为分离
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
取消一个线程
int pthread_cancel(pthread_t thread); 杀死一个线程
void pthread_testcancel(void); //手动加入一个取消点
int pthread_setcancelstate(int state, int *oldstate);
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
int pthread_setcanceltype(int type, int *oldtype);
PTHREAD_CANCEL_DEFERRED
PTHREAD_CANCEL_ASYNCHRONOUS
线程的清理
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
线程的同步和互斥
互斥机制
mutex互斥锁 读写锁
任务访问临界资源前申请锁,访问完后释放锁
互斥锁
互斥锁(Mutex,Mutual Exclusion)是一种线程同步机制,用于确保在同一时刻只有一个线程能访问或修改共享资源,以避免多线程并发访问共享资源时引起数据不一致或竞争条件。
互斥锁初始化 – pthread_mutex_init
动态初始化
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t * attr);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
attr 互斥锁属性,NULL表示缺省属性
man 函数出现 No manual entry for pthread_mutex_xxx解决办法
apt-get install manpages-posix-dev
静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //初始化互斥量
互斥锁销毁 pthread_mutex_destroy
申请锁 – pthread_mutex_lock
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex)
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
pthread_mutex_lock 如果无法获得锁,任务阻塞
pthread_mutex_trylock 如果无法获得锁,返回EBUSY而不是挂起等待
释放锁 – pthread_mutex_unlock
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
读写锁
读写锁(Read-Write Lock)是一种线程同步机制,允许多个线程同时读取共享资源,但在写入时,只有一个线程可以独占访问资源。这种机制的优点在于提高并发性能,特别是在读多写少的场景下。与互斥锁不同,读写锁允许并发读操作,从而提升了系统的吞吐量。
读写锁的工作原理
读锁(共享锁):多个线程可以同时持有读锁,从而允许多个线程同时读取数据。只要没有线程在写入,读操作可以并发执行。
写锁(独占锁):写操作需要独占锁。只有在没有任何线程持有读锁或写锁时,写锁才能被获取。写锁排斥其他读写操作,直到写入完成并释放锁。
初始化一个读写锁 pthread_rwlock_init
读锁定读写锁 pthread_rwlock_rdlock
非阻塞读锁定 pthread_rwlock_tryrdlock
写锁定读写锁 pthread_rwlock_wrlock
非阻塞写锁定 pthread_rwlock_trywrlock
解锁读写锁 pthread_rwlock_unlock
释放读写锁 pthread_rwlock_destroy
死锁
各自持有对方的锁导致的死锁问题。
条件变量
条件变量(Condition Variable)是一种线程同步机制,通常与互斥锁一起使用,用于在多线程环境下实现线程之间的等待和通知机制。条件变量可以让线程在等待某个特定条件时挂起,并在条件满足后被唤醒,从而避免不必要的忙等待,提高线程运行效率。
条件变量的工作原理
等待条件:线程调用 wait 等待某个条件,进入阻塞状态。此时条件变量会与一个互斥锁配合使用,以确保在等待条件时不会发生资源竞争。
通知条件:当条件满足时,另一个线程可以调用 signal 或 broadcast 来通知等待的线程继续运行。
signal:唤醒一个等待中的线程。
broadcast:唤醒所有等待中的线程。
使用场景
条件变量适用于需要等待某一特定条件的多线程场景。例如:
生产者-消费者模型:当生产者线程添加数据时,通过条件变量通知消费者线程开始消费。
资源池:线程在资源不足时等待,其他线程在资源充足时通知它们。
pthread_cond_wait(&m_cond,&m_mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
注意事项
条件变量与互斥锁结合:条件变量的等待和通知操作通常需要和互斥锁一起使用,以防止出现竞态条件。
虚假唤醒:即使条件没有满足,线程也可能被唤醒,因此 pthread_cond_wait 必须在循环中使用以反复检查条件。
线程池的概念
线程池是一种多线程管理模式,用于限制并优化多线程的使用。它创建一组预先初始化的线程并将任务提交给这些线程,避免频繁的线程创建和销毁开销,从而提升系统性能和资源利用率。
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>#define POOL_NUM 4typedef struct Task
{void (*func)(int);int arg;struct Task *next;
} Task;typedef struct ThreadPool
{pthread_mutex_t taskLock;pthread_cond_t newTask;pthread_cond_t taskDone;pthread_t tid[POOL_NUM];Task *queue_head;Task *queue_tail;int busywork;int task_count;int shutdown; // 标志线程池是否关闭
} ThreadPool;ThreadPool *pool;void *threadFunction(void *arg)
{while (1){pthread_mutex_lock(&pool->taskLock);while (pool->queue_head == NULL && !pool->shutdown){pthread_cond_wait(&pool->newTask, &pool->taskLock);}if (pool->shutdown){pthread_mutex_unlock(&pool->taskLock);pthread_exit(NULL);}Task *task = pool->queue_head;pool->queue_head = task->next;if (pool->queue_head == NULL){pool->queue_tail = NULL;}pool->busywork++;pool->task_count--;pthread_mutex_unlock(&pool->taskLock);task->func(task->arg);// 目前任务还有多少个printf("task_count:%d\n", pool->task_count);free(task);pthread_mutex_lock(&pool->taskLock);pool->busywork--;if (pool->busywork == 0 && pool->task_count == 0){pthread_cond_signal(&pool->taskDone);}pthread_mutex_unlock(&pool->taskLock);}return NULL;
}void pool_add_task(void (*func)(int), int arg)
{Task *newTask = malloc(sizeof(Task));if (newTask == NULL){perror("Failed to allocate memory for new task");return;}newTask->func = func;newTask->arg = arg;newTask->next = NULL;pthread_mutex_lock(&pool->taskLock);if (pool->queue_tail == NULL){pool->queue_head = newTask;pool->queue_tail = newTask;}else{pool->queue_tail->next = newTask;pool->queue_tail = newTask;}pool->task_count++;pthread_cond_signal(&pool->newTask);pthread_mutex_unlock(&pool->taskLock);
}void initThreadPool()
{pool = malloc(sizeof(ThreadPool));pthread_mutex_init(&pool->taskLock, NULL);pthread_cond_init(&pool->newTask, NULL);pthread_cond_init(&pool->taskDone, NULL);pool->queue_head = NULL;pool->queue_tail = NULL;pool->busywork = 0;pool->task_count = 0;pool->shutdown = 0;for (int i = 0; i < POOL_NUM; i++){pthread_create(&pool->tid[i], NULL, threadFunction, NULL);}
}void destroyThreadPool()
{pthread_mutex_lock(&pool->taskLock);while (pool->task_count > 0 || pool->busywork > 0){pthread_cond_wait(&pool->taskDone, &pool->taskLock);}pool->shutdown = 1;pthread_cond_broadcast(&pool->newTask);pthread_mutex_unlock(&pool->taskLock);for (int i = 0; i < POOL_NUM; i++){pthread_join(pool->tid[i], NULL);}pthread_mutex_destroy(&pool->taskLock);pthread_cond_destroy(&pool->newTask);pthread_cond_destroy(&pool->taskDone);free(pool);
}void realwork(int arg)
{printf("Processing task with arg: %d\n", arg);sleep(1); // Simulate work
}int main()
{initThreadPool();for (int i = 0; i < 100; i++){pool_add_task(realwork, i);}destroyThreadPool();return 0;
}