Linux系统编程学:互斥锁与信号量
线程控制一. 互斥锁什么是互斥1.概念 在多线程中对临界资源的排他性访问。2.临界资源在多线程中会对这个资源进行读写操作的。全局变量文件设备。3.排他访问在多线程中只能有一个线程进行读写操作的。用途4.在多线程中一个资源同一时刻只能有一个线程th1,th2, 是两个线程是并发运行。也就是代码在运行是是穿插进行的。如果A这个指令翻译成汇编是3句(至少)。在一次线程的调度这3句汇编不一定一次性执行。也就是说可呢个th1 执行了 1,2步就切换到th2 执行。这个时候就会发生错误(数据的一致行性出现问题)互斥锁的使用步骤定义互斥锁 初始化锁 加锁 解锁 销毁2、初始化锁pthread_mutex_t mutex;int pthread_mutex_init(pthread_mutex_tmutex,const pthread_mutexattr_tattr)功能将已经定义好的互斥锁初始化。参数mutex 要初始化的互斥锁atrr初始化的值一般是NULL表示默认与互斥锁的关系和区别互斥包含同步同步是互斥的一个特例3、加锁int pthread_mutex_lock(pthread_mutex_t *mutex);**功能**用指定的互斥锁开始加锁代码参数 mutex 用来给代码加锁的互斥锁注意1.加锁后的代码到解锁部分的代码属于原子操作2.在加锁期间其他进程/线程都不能操作该部分代码3.如果该函数在执行的时候mutex已经被其他部分使用则代码阻塞。4.原子操作 在线程的一次调度中这段代码必须完成不能发生线程调度。4、解锁int pthread_mutex_unlock(pthread_mutex_t *mutex);功能将指定的互斥锁解锁。参数用来解锁的互斥锁解锁之后代码不再排他访问一般加锁解锁同时出现5、销毁int pthread_mutex_destroy(pthread_mutex_t *mutex);功能使用互斥锁完毕后需要销毁互斥锁参数mutex 要销毁的互斥锁只要是多线程共享的全局变量读和写都要加同一把锁保护死锁产生死锁的四个必要条件1 互斥条件一个资源每次只能被一个进程使用。2 请求与保持条件一个进程因请求资源而阻塞时对已获得的资源保持不放。3 不剥夺条件:进程已获得的资源在末使用完之前不能强行剥夺。4 循环等待条件:若干进程之间形成一种头尾相接的循环等注意加锁范围尽量小只包住真正访问共享数据的语句锁内不要做 IO、sleep 等耗时操作。避免死锁多个锁时约定统一的加锁顺不要重复加锁默认互斥锁不可重入同一线程重复 lock 会死锁4. 信号量信号量本质是一个带原子操作的计数器sem_wait计数减 1若为 0 则阻塞等待sem_post计数加 1并唤醒等待者。POSIX 信号量有两套接口无名信号量 semaphore.h线程间常用和有名信号量 sem_open可跨进程。这里用无名信号量。1、信号量的定义 sem_t sem;2、信号量的初始化int sem_init(sem_t *sem, int pshared, unsigned int value);功能将已经定义好的信号量赋值。参数sem 要初始化的信号量pshared 0 ;表示线程间使用信号量!0 ;表示进程间使用信号量value信号量的初始值一般无名信号量都是二值信号量0 10 表示红灯进程暂停阻塞1 表示绿灯进程可以通过执行返回值成功 0失败 -13、信号量的PV 操作P 》申请资源》申请一个二值信号量V 》释放资源》释放一个二值信号量P操作对应函数 》sem_wait();V操作对应函数 》sem_post();int sem_wait(sem_t *sem);功能判断当前sem信号量是否有资源可用。如果sem有资源(1)则申请该资源程序继续运行如果sem没有资源(0)则线程阻塞等待一旦有资源种类则自动申请资源并继续运行程序。注意sem 申请资源后会自动执行 sem sem - 1;参数sem 要判断的信号量资源int sem_post(sem_t *sem);功能函数可以将指定的sem信号量资源释放并默认执行sem sem1;线程在该函数上不会阻塞。参数sem 要释放资源的信号量返回值成功 0失败值 -14、信号量的销毁int sem_destroy(sem_t *sem);功能使用完毕将指定的信号量销毁参数sem要销毁的信号量返回值成功 0失败值 -1用信号量实现互斥#includepthread.h#includesemaphore.h#includestdio.hintcounter0;sem_tsem;void*add(void*arg){for(inti0;i1000000;i){sem_wait(sem);counter;sem_post(sem);}returnNULL;}intmain(void){pthread_tt1,t2;sem_init(sem,0,1);// 第二个参数 0 表示线程间共享初值 1 即互斥pthread_create(t1,NULL,add,NULL);pthread_create(t2,NULL,add,NULL);pthread_join(t1,NULL);pthread_join(t2,NULL);sem_destroy(sem);printf(counter %d\n,counter);return0;}生产者—消费者#includepthread.h#includesemaphore.h#includestdio.h#defineN5intbuffer[N];intin0,out0;sem_tempty_sem;// 空槽位数量sem_tfull_sem;// 已填充数量pthread_mutex_tlockPTHREAD_MUTEX_INITIALIZER;void*producer(void*arg){for(inti0;i10;i){sem_wait(empty_sem);// 等一个空位pthread_mutex_lock(lock);buffer[in]i;in(in1)%N;pthread_mutex_unlock(lock);sem_post(full_sem);// 通知有新数据printf(生产 %d\n,i);}returnNULL;}void*consumer(void*arg){for(inti0;i10;i){sem_wait(full_sem);// 等有数据pthread_mutex_lock(lock);intvbuffer[out];out(out1)%N;pthread_mutex_unlock(lock);sem_post(empty_sem);// 归还空位printf(消费 %d\n,v);}returnNULL;}intmain(void){pthread_tp,c;sem_init(empty_sem,0,N);sem_init(full_sem,0,0);pthread_create(p,NULL,producer,NULL);pthread_create(c,NULL,consumer,NULL);pthread_join(p,NULL);pthread_join(c,NULL);sem_destroy(empty_sem);sem_destroy(full_sem);pthread_mutex_destroy(lock);return0;}这里的分工很清晰信号量负责「有没有」的计数与阻塞互斥锁负责「改缓冲区」的原子性。两者不能互相替代。5. 三者对比与选择原语语义典型场景线程并发执行流并行计算、IO 并发、任务拆分互斥锁二值、有所有权保护共享变量、临界区信号量计数、无所有权资源池限流、生产者—消费者、事件通知选择建议只是保护一段共享数据 → 互斥锁语义最直接。需要控制「同时最多 N 个」或协调生产/消费节奏 → 信号量。需要等待某个条件成立如数据就绪→ 条件变量 pthread_cond_t 往往比信号量更合适。##注意没有 pthread_join程序提前退出导致线程没跑完输出结果不稳定。在锁内调用 printf虽然能跑但严重拖慢性能后来把打印挪到锁外。信号量初值写错empty_sem 应为缓冲区大小full_sem应为 0写反后消费者直接卡死。编译时漏了 -lpthread报 undefined reference to pthread_create。