linux-线程编程
一、核心知识点介绍线程概念线程是进程内的一个执行流是操作系统进行 CPU 调度的基本单位。同一个进程内的多个线程共享进程的地址空间代码段、数据段、堆、打开的文件描述符等。每个线程拥有自己独立的栈空间、程序计数器 (PC) 和线程本地存储 (TLS)。线程比进程更轻量级创建和切换开销更小更适用于并发任务。POSIX 线程 (pthreads)Linux 主要使用 POSIX 线程标准pthread来实现线程编程。相关的函数和数据类型都定义在头文件pthread.h中。编译时需要链接pthread库通常使用-pthread或-lpthread编译选项。线程创建与终止创建线程int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);thread: 指向新线程 ID 的指针。attr: 线程属性对象通常设为NULL使用默认属性。start_routine: 新线程要执行的函数返回void*参数为void*。arg: 传递给start_routine的参数。返回值成功返回 0失败返回错误码。等待线程结束int pthread_join(pthread_t thread, void **retval);thread: 要等待的线程 ID。retval: 用于接收目标线程的退出状态start_routine的返回值。返回值成功返回 0失败返回错误码。调用线程会阻塞直到目标线程终止。线程终止线程函数start_routine执行return语句。在线程内部调用void pthread_exit(void *retval);显式退出。主线程调用exit或main函数返回会导致整个进程终止所有线程也随之结束不建议。分离线程int pthread_detach(pthread_t thread);将线程标记为分离状态。分离状态的线程终止时其资源会自动回收无需其他线程调用pthread_join。不能在已分离的线程上调用pthread_join。线程同步多个线程并发访问共享资源时需要同步机制来保证数据的一致性和避免竞态条件。互斥锁 (Mutex):用于保护临界区确保同一时间只有一个线程能访问共享资源。初始化静态初始化 (全局或静态变量)pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;动态初始化int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);(通常attr设为NULL)加锁int pthread_mutex_lock(pthread_mutex_t *mutex);(阻塞)尝试加锁int pthread_mutex_trylock(pthread_mutex_t *mutex);(非阻塞)解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);销毁int pthread_mutex_destroy(pthread_mutex_t *mutex);(动态初始化的需要销毁)条件变量 (Condition Variable):用于线程间的条件等待。通常与互斥锁配合使用。允许线程在某个条件不满足时阻塞等待直到其他线程改变条件并发出信号。初始化静态初始化pthread_cond_t cond PTHREAD_COND_INITIALIZER;动态初始化int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);等待条件int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);该函数会原子性地释放mutex并阻塞当前线程。在被唤醒后会重新获取mutex锁再返回。注意条件检查应在循环中进行因为可能存在虚假唤醒 (spurious wakeup)。唤醒等待线程int pthread_cond_signal(pthread_cond_t *cond);- 唤醒至少一个等待该条件的线程。int pthread_cond_broadcast(pthread_cond_t *cond);- 唤醒所有等待该条件的线程。销毁int pthread_cond_destroy(pthread_cond_t *cond);线程安全与可重入线程安全一个函数或代码段可以被多个线程同时调用而不会产生错误结果。可重入一个函数可以在其自身尚未执行完毕时再次被调用例如信号处理函数调用它。可重入函数必然是线程安全的但线程安全函数不一定是可重入的例如使用全局静态数据的线程安全函数。编写线程安全代码的关键避免使用全局或静态变量或使用同步机制保护对它们的访问使用局部变量或线程本地存储 (TLS)。线程取消一个线程可以请求取消另一个线程 (int pthread_cancel(pthread_t thread);)。目标线程可以设置取消状态 (int pthread_setcancelstate(int state, int *oldstate);) 和取消类型 (int pthread_setcanceltype(int type, int *oldtype);) 来控制如何响应取消请求。线程可以选择在取消点 (pthread_testcancel();) 或在接收到取消请求时立即退出。可以使用清理处理程序 (pthread_cleanup_push();,pthread_cleanup_pop();) 在取消时释放资源。二、技术代码示例示例 1基本线程创建与等待#includestdio.h#includestdlib.h#includepthread.h// 线程函数void*print_message(void*ptr){char*message(char*)ptr;printf(%s\n,message);returnNULL;// 线程正常结束}intmain(){pthread_tthread1,thread2;constchar*message1Thread 1;constchar*message2Thread 2;// 创建线程1if(pthread_create(thread1,NULL,print_message,(void*)message1)){fprintf(stderr,Error creating thread1\n);return1;}// 创建线程2if(pthread_create(thread2,NULL,print_message,(void*)message2)){fprintf(stderr,Error creating thread2\n);return1;}// 等待线程结束pthread_join(thread1,NULL);pthread_join(thread2,NULL);printf(Main thread exiting.\n);return0;}编译:gcc -o basic_thread basic_thread.c -pthread示例 2使用互斥锁保护共享计数器#includestdio.h#includepthread.h#defineNUM_THREADS5#defineMAX_COUNT100000intcounter0;// 共享计数器pthread_mutex_tcounter_mutexPTHREAD_MUTEX_INITIALIZER;// 保护计数器的互斥锁void*increment_counter(void*arg){for(inti0;iMAX_COUNT/NUM_THREADS;i){pthread_mutex_lock(counter_mutex);// 进入临界区前加锁counter;// 修改共享变量pthread_mutex_unlock(counter_mutex);// 离开临界区后解锁}returnNULL;}intmain(){pthread_tthreads[NUM_THREADS];// 创建多个线程for(inti0;iNUM_THREADS;i){if(pthread_create(threads[i],NULL,increment_counter,NULL)){perror(pthread_create);return1;}}// 等待所有线程结束for(inti0;iNUM_THREADS;i){pthread_join(threads[i],NULL);}// 销毁互斥锁 (静态初始化通常不需要显式销毁但习惯上可以加)pthread_mutex_destroy(counter_mutex);printf(Final counter value: %d (Expected: %d)\n,counter,MAX_COUNT);return0;}示例 3使用条件变量实现生产者-消费者模型 (简化版)#includestdio.h#includestdlib.h#includepthread.h#defineBUFFER_SIZE10intbuffer[BUFFER_SIZE];// 共享缓冲区intcount0;// 当前缓冲区中元素数量intin0;// 生产者放入位置intout0;// 消费者取出位置pthread_mutex_tmutexPTHREAD_MUTEX_INITIALIZER;// 保护缓冲区的互斥锁pthread_cond_tcond_producerPTHREAD_COND_INITIALIZER;// 生产者条件变量 (缓冲区有空位)pthread_cond_tcond_consumerPTHREAD_COND_INITIALIZER;// 消费者条件变量 (缓冲区有数据)void*producer(void*arg){for(inti0;i20;i){// 生产20个数据pthread_mutex_lock(mutex);// 等待缓冲区有空位 (使用while循环防止虚假唤醒)while(countBUFFER_SIZE){pthread_cond_wait(cond_producer,mutex);}// 生产数据放入缓冲区buffer[in]i;printf(Produced: %d\n,i);in(in1)%BUFFER_SIZE;count;// 通知消费者可能有数据了pthread_cond_signal(cond_consumer);pthread_mutex_unlock(mutex);// 模拟生产耗时// usleep(rand() % 100000);}returnNULL;}void*consumer(void*arg){for(inti0;i20;i){// 消费20个数据pthread_mutex_lock(mutex);// 等待缓冲区有数据while(count0){pthread_cond_wait(cond_consumer,mutex);}// 消费数据从缓冲区取出intitembuffer[out];printf(Consumed: %d\n,item);out(out1)%BUFFER_SIZE;count--;// 通知生产者可能有空位了pthread_cond_signal(cond_producer);pthread_mutex_unlock(mutex);// 模拟消费耗时// usleep(rand() % 200000);}returnNULL;}intmain(){pthread_tprod_thread,cons_thread;srand(time(NULL));// 初始化随机数种子 (用于模拟耗时)// 创建生产者和消费者线程if(pthread_create(prod_thread,NULL,producer,NULL)){perror(pthread_create producer);return1;}if(pthread_create(cons_thread,NULL,consumer,NULL)){perror(pthread_create consumer);return1;}// 等待线程结束pthread_join(prod_thread,NULL);pthread_join(cons_thread,NULL);// 清理 (静态初始化通常不需要显式销毁)pthread_mutex_destroy(mutex);pthread_cond_destroy(cond_producer);pthread_cond_destroy(cond_consumer);return0;}三、注意事项错误处理始终检查pthread_*函数的返回值它们失败时会返回非零错误码可通过strerror查看。资源泄露确保创建的线程被正确等待 (pthread_join) 或分离 (pthread_detach)避免僵尸线程。动态初始化的互斥锁和条件变量需要销毁 (pthread_mutex_destroy,pthread_cond_destroy)。死锁仔细设计锁的获取顺序避免循环等待。竞态条件使用同步机制互斥锁、条件变量保护所有对共享资源的访问。虚假唤醒在使用pthread_cond_wait时必须在循环中检查条件因为即使没有收到信号等待也可能返回。性能锁操作有开销尽量减少临界区的范围。考虑使用读写锁 (pthread_rwlock_t) 或原子操作 (C11_Atomic或 GCC__sync_*,__atomic_*内置函数) 优化特定场景。1. 线程属性线程属性Thread Attributes用于在创建线程时配置其行为。通过设置属性可以控制线程的栈大小、调度策略、是否可分离等特性。关键属性栈大小pthread_attr_setstacksize()可设置线程栈大小。调度策略pthread_attr_setschedpolicy()设置调度策略如SCHED_FIFO、SCHED_RR。可分离状态pthread_attr_setdetachstate()设置线程为可分离PTHREAD_CREATE_DETACHED或可连接PTHREAD_CREATE_JOINABLE。示例POSIX#includepthread.hintmain(){pthread_attr_tattr;pthread_attr_init(attr);pthread_attr_setdetachstate(attr,PTHREAD_CREATE_DETACHED);// 设置为可分离pthread_ttid;pthread_create(tid,attr,thread_func,NULL);pthread_attr_destroy(attr);return0;}2. 线程本地存储TLS线程本地存储Thread-Local Storage, TLS允许每个线程拥有独立的变量副本避免共享数据的竞争条件。实现方式C/C使用thread_local关键字C11/C11。POSIXpthread_key_create()创建键pthread_setspecific()和pthread_getspecific()设置/获取值。示例C#includeiostream#includethreadthread_localintcounter0;// 每个线程独立副本voidincrement(){counter;std::coutThread std::this_thread::get_id(): counter counterstd::endl;}intmain(){std::threadt1(increment);std::threadt2(increment);t1.join();t2.join();return0;}3. 屏障Barrier屏障Barrier用于同步多个线程的执行确保所有线程到达某一点后才继续执行。操作逻辑初始化屏障指定等待的线程数NNN。每个线程调用pthread_barrier_wait()等待。当第NNN个线程到达时所有线程继续执行。示例POSIX#includepthread.hpthread_barrier_tbarrier;void*thread_func(void*arg){// ... 执行任务pthread_barrier_wait(barrier);// 等待其他线程// ... 继续执行returnNULL;}intmain(){pthread_barrier_init(barrier,NULL,3);// 等待3个线程pthread_tt1,t2,t3;pthread_create(t1,NULL,thread_func,NULL);pthread_create(t2,NULL,thread_func,NULL);pthread_create(t3,NULL,thread_func,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_barrier_destroy(barrier);return0;}4. 读写锁读写锁Read-Write Lock允许多个线程同时读取数据但写入时独占访问。适用于读多写少的场景。操作逻辑读锁多个线程可同时获取读锁。写锁仅一个线程可获取写锁且与读/写锁互斥。示例POSIX#includepthread.hpthread_rwlock_trwlockPTHREAD_RWLOCK_INITIALIZER;void*reader(void*arg){pthread_rwlock_rdlock(rwlock);// 获取读锁// ... 读操作pthread_rwlock_unlock(rwlock);returnNULL;}void*writer(void*arg){pthread_rwlock_wrlock(rwlock);// 获取写锁// ... 写操作pthread_rwlock_unlock(rwlock);returnNULL;}5. 信号量信号量Semaphore用于控制对共享资源的访问数量支持计数信号量非二进制。核心操作P操作sem_wait申请资源信号量减1。V操作sem_post释放资源信号量加1。示例POSIX#includesemaphore.hsem_tsem;void*worker(void*arg){sem_wait(sem);// P操作// ... 访问资源sem_post(sem);// V操作returnNULL;}intmain(){sem_init(sem,0,3);// 初始化信号量允许3个线程同时访问pthread_tt1,t2,t3;pthread_create(t1,NULL,worker,NULL);pthread_create(t2,NULL,worker,NULL);pthread_create(t3,NULL,worker,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);sem_destroy(sem);return0;}对比总结机制适用场景特点线程属性线程配置管理控制栈大小、调度策略等TLS线程私有数据避免竞争每个线程独立副本Barrier多阶段并行任务同步等待所有线程到达指定点读写锁读多写少的资源访问读共享写独占信号量控制并发资源访问数量支持计数灵活控制并发度这些同步机制在多线程编程中各有侧重需根据实际场景选择最合适的工具。