unix环境高级编程--线程同步

线程同步手段

a) 概要

i. 包括互斥量、读写锁、条件变量、自旋锁、屏障。

ii. 上述锁都可以通过各自的属性对象设置锁的属性,并且都具有进程共享属性。所谓进程共享属性:也即进程们通过nmap享有一块共享内存。当需要对共享内存中的东西进行同步时,就需要锁,开启锁们的进程共享属性,就可以在共享内存中分配锁,从而达到同步多进程访问共享内存的某个东西的效果。

iii. 进阶使用建议

1. 尽量最低限度的共享对象,减少同步的场合

2. 尽量使用高级并发构建

3. 不得不使用底层同步原语时,只用非递归的互斥量和条件变量;慎用读写锁;不用信号量

 

b) 互斥量mutex加锁原语】【互斥机制】

i. 使用示例

1. 定义:pthread_mutex_t  fp;

2. 初始化:静态分配的直接用宏PTHREAD_MUTEX_INITIALIZER初始化。用malloc动态分配的必须用pthread_mutex_init( fp , attr )来初始化

3. 使用:       pthread_mutex_lock( &fp )//上锁

do somethings····

Pthread_mutex_unlock( &fp )//解锁

4. 销毁:使用malloc分配内存的锁需要pthread_mutex_destroy( fp )来销毁

ii. 简要说明

1. connect一样,互斥量的上锁pthread_mutex_lock的使用也是阻塞的。

2. 如果线程不希望被阻塞,可以使用pthread_mutex_trylock( fp ),如果互斥量未被锁上,try操作会锁上互斥量并返回0;如果互斥量已经被锁上就会返回EBUSY

3. 使用pthread_mutex_timedlock( fp , time )可以在time时间内阻塞等待互斥量,超出time,返回ETIMEOUT

iii. 进阶使用

1. 使用RAII手法来封装互斥锁的创建、销毁、加锁、解锁

a) 无需再记忆互斥量与需保护临界区的配对问题

b) 防止在线程A中加锁,在线程B中解锁的现象。做到scoped locking

c) 无需担心忘记解锁导致死锁,过了scope对象销毁,锁会自动释放

d) 无需担心重复解锁

2. 只使用非递归的mutex

a) 递归的互斥量不会导致一个线程会自己把自己给锁死。非递归互斥量递归调用互斥量会死锁。死锁更容易暴露程序的逻辑错误,死锁比较容易debug,把各个线程的调用栈打出来(注意GDB函数调用栈的打印)

3. 不使用跨进程的mutex

 

c) 条件变量【也叫管程】【等待原语】【同步机制】

i. 简要说明

1. 条件变量使线程以无竞争的方式等待特定条件的发生,pthread_cond_wait会将该线程移至等待条件的线程列表上,并释放已加锁的互斥量。这样线程在等待时就可以不去竞争互斥锁了。然后从wait阻塞返回之后再加锁

While(1)

{

Pthread_mutex_lock( fp );//需要竞争互斥量去查看条件好了没

If(条件好了)//检查条件好了没···

Do something

Pthread_mutex_unlock( fp );

}

pthread_cond_wait后,等待pthread_cond_singal唤醒就好。

2. Pthread_cond_wait使用时除条件变量外需要一个已经加锁的mutex

 

ii. 使用示例

Pthread_mutex_t fp=PTHREAD_MUTEX_INITIALIZER;

Pthread_cond_t cond=PTHREAD_COND_INITIALIZER;

线程A pthread_mutex_lock( &fp );

pthread_cond_wait( &cond , &fp );//fp需要事先加锁

//Do something···

Pthread_mutex_unlock( &fp );//wait返回时重新加锁所以需要解锁

线程B

Pthread_mutex_lock( &fp );//wait期间可以使用互斥量fp

Do something····

Pthread_mutex_unlock( &fp );

Pthread_cond_singal(&cond);//唤醒

iii. 进阶使用

1. 需要使用互斥量来保护条件变量!!!

2. 需要注意【虚假唤醒】!!!不能使用if,而是要用while

a) while(条件不满足)

pthread_cond_wait();(详细例子anp 335页)

b) 在多核处理器下,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回。这种效应成为”虚假唤醒”(spurious wakeup)。

b) 读写锁reader-writer lock

i. 简要说明

1. 读写锁有三种状态。读加锁状态,写加锁状态和不加锁状态。

a) 在写加锁状态下,在这个锁被解锁之前任何试图对这个锁加锁的线程都会被阻塞

b) 在读加锁状态下,在这个锁被解锁之前任何对这个锁加读锁都会成功;任何最这个锁加写锁会被阻塞,直到所有读锁被解除

2. 等待加锁会阻塞线程

3. 类似的也有pthread_rwlock_timedrdlockpthread_rwlock_timedwrlock

ii. 使用示例

1. 定义:pthread_rwlock_t qlock;

2. 初始化:必须用pthread_rwlock_init来初始化

3. 使用: pthread_rwlock_rdlock( &qlock )

pthread_rwlock_wrlock( &qlock )

pthread_rwlock_unlock( &qlock )

4. 销毁:使用完之后必须用pthread_rwlock_destory来销毁

iii. 进阶使用

1. 【慎用读写锁】。常见的错误是:在持有readlock的时候修改了共享数据

 

 

c) 信号量(进程间的同步手段)

i. 本质上是一个非负整数的计数器,公共资源释放增加的时候sem_post增加信号量,sem_wait减少信号量。可做进程间同步工具和线程间同步工具。和条件变量的不同在于条件变量是使线程休眠,然后被其他线程唤醒;而信号量是计数公共资源,当公共资源为0是,sem_wait会阻塞

ii. 使用示例

sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT); //创建或获取信号量。和互斥量他们不同,key,和消息队列相同。信号量可不是什么自动变量!!!

union semun sem_union; //初始化信号量

sem_union.val = 1;  

semctl(sem_id, 0, SETVAL, sem_union)

struct sembuf sem_b;  //对信号量做减1操作,即等待Psv

sem_b.sem_num = 0;  

sem_b.sem_op = -1;//P()  

sem_b.sem_flg = SEM_UNDO;  

semop(sem_id, &sem_b, 1)

//访问资源

struct sembuf sem_b;//释放操作,它使信号量变为可用,即发送信号Vsv  

sem_b.sem_num = 0;  

sem_b.sem_op = 1;//V()  

sem_b.sem_flg = SEM_UNDO;  

semop(sem_id, &sem_b, 1)

iii. 进阶使用

1. 【不要使用信号量】。互斥量+条件变量可以很好的代替信号量的功能

d) 自旋锁

i. 简要说明

1. 自旋锁和互斥锁有点相似,但自旋锁不会引起调用者的睡眠(互斥锁,加锁不成功时,会阻塞在那儿,调用线程也会进入睡眠),如果自旋锁已经被别的线程保持,那么调用者会一直循环在那里,检查锁标志位,直至自旋锁的保持者释放了锁(也即自旋锁加锁不成功,不会导致线程进入睡眠,而是忙等,循环检查锁标志)。

2. 自旋锁的忙等特性,使它适合场景:持锁时间要短且线程不希望在重新调度上花太多的成本

3. 在自旋锁保护的临界区代码不要有可能会进入休眠的函数,这会导致其他等待自旋锁自旋的线程的等待时间过长,导致浪费CPU资源。

4. 自旋锁会阻塞中断(因为中断也需要先获取这个已经被阻塞的自旋锁),所以被保护的临界区不希望被中断打扰时可以使用自旋锁。

a) 触发内核态1:系统调用

b) 触发内核态2:中断(软中断、硬中断)

c) 触发用户态:执行用户级别的代码

d) 内核态1:运行于进程上下文,内核代表进程运行在内核空间

e) 内核态2:运行于中断上下文,内核代表硬件运行于内核空间

f) 用户态:运行于用户空间

g) 进程上下文:系统调用时,用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境。

h) 中断上下文:中断发生时,用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境

ii. 使用示例

Pthread_spinlock_t *q=malloc( sizeof( Pthread_spinlock_t ) );

Pthread_spin_init(q);

Pthread_spin_lock(q);

Do something···

Pthread_spin_unlock(q);

 

 

e) 屏障

i. 简要说明

1. 屏障允许每个线程等待,直到所有的合作线程都达到某个点,然后从该点继续执行。

2. 使用场景:多路并行的归并排序

ii. 使用示例

Pthread_barrier_t b;

Pthread_barrier_init( &b, NULL, 8);//8为屏障计数,即8wait到了之后才继续执行

For(int i=0;i<7;++i)

     Pthread_create(NULL , NULL , NULL , sort , i*num );//sort函数中有wait,共7wait

Pthread_barrier_wait(&b);

Merge();//大合并



你可能感兴趣的:(unix环境编程与网络编程)