一、posix 信号量
信号量的概念参见这里。前面也讲过system v 信号量,现在来说说posix 信号量。
system v 信号量只能用于进程间同步,而posix 信号量除了可以进程间同步,还可以线程间同步。system v 信号量每次PV操作可以是N,但Posix 信号量每次PV只能是1。除此之外,posix 信号量还有命名和匿名之分(man 7 sem_overview):
1、命名信号量
名字以/somename 形式分辨,只能有一个/ ,且总长不能超过NAME_MAX - 4(一般是251)。
需要用sem_open 函数创建或打开,PV操作分别是sem_wait 和 sem_post,可以使用sem_close 关闭,删除用sem_unlink。
命名信号量用于不共享内存的进程间同步(内核实现),类似system v 信号量。
2、匿名信号量
存放在一块共享内存中,如果是线程共享,这块区域可以是全局变量;如果是进程共享,可以是system v 共享内存(shmget 创建,shmat 映射),也可以是 posix 共享内存(shm_open 创建,mmap 映射)。
匿名信号量必须用sem_init 初始化,sem_init 函数其中一个参数pshared决定了线程共享还是进程共享,也可以用sem_post 和sem_wait 进行操作,在共享内存释放前,匿名信号量要先用sem_destroy 销毁。
有关这些函数的具体参数可以man 一下。
二、互斥锁
对于多线程的程序,访问冲突的问题是很普遍的,解决的办法是引入互斥锁(Mutex,MutualExclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
Mutex用pthread_mutex_t类型的变量表示,pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果attr为NULL则表示缺省属性,具体看结构体:有人建议开发中总是设置 PTHREAD_MUTEX_RECURSIVE 属性,避免死锁。
// 互斥量属性: 同一线程可多次加锁 pthread_mutexattr_t m_attr; pthread_mutexattr_init(&m_attr); pthread_mutexattr_settype(&m_attr, PTHREAD_MUTEX_RECURSIVE);
struct pthread_mutexattr_t
{
enum lock_type // 使用pthread_mutexattr_settype来更改
{
PTHREAD_MUTEX_TIMED_NP [default]//当一个线程加锁后,其余请求锁的线程形成等待队列,在解锁后按优先级获得锁。
PTHREAD_MUTEX_ADAPTIVE_NP // 动作最简单的锁类型,解锁后所有线程重新竞争。
PTHREAD_MUTEX_RECURSIVE_NP // 允许同一线程对同一锁成功获得多次。当然也要解锁多次。其余线程在解锁时重新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP // 若同一线程请求同一锁,返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP动作相同。
} type;
} attr;
用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
上面的具体函数可以man 一下。
三、生产者消费者问题
生产者消费者问题概念参见这里。下面使用posix 信号量和互斥锁一起来演示:
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define CONSUMERS_COUNT 1
#define PRODUCERS_COUNT 1
#define BUFFSIZE 10
int g_buffer[BUFFSIZE];
unsigned short in = 0;
unsigned short out = 0;
unsigned short produce_id = 0;
unsigned short consume_id = 0;
sem_t g_sem_full;
sem_t g_sem_empty;
pthread_mutex_t g_mutex;
pthread_t g_thread[CONSUMERS_COUNT + PRODUCERS_COUNT];
void *consume(void *arg)
{
int i;
int num = (int)arg;
while (1)
{
printf("%d wait buffer not empty\n", num);
sem_wait(&g_sem_empty);
pthread_mutex_lock(&g_mutex);
for (i = 0; i < BUFFSIZE; i++)
{
printf("%02d ", i);
if (g_buffer[i] == -1)
printf("%s", "null");
else
printf("%d", g_buffer[i]);
if (i == out)
printf("\t<--consume");
printf("\n");
}
consume_id = g_buffer[out];
printf("%d begin consume product %d\n", num, consume_id);
g_buffer[out] = -1;
out = (out + 1) % BUFFSIZE;
printf("%d end consume product %d\n", num, consume_id);
pthread_mutex_unlock(&g_mutex);
sem_post(&g_sem_full);
sleep(1);
}
return NULL;
}
void *produce(void *arg)
{
int num = (int)arg;
int i;
while (1)
{
printf("%d wait buffer not full\n", num);
sem_wait(&g_sem_full);
pthread_mutex_lock(&g_mutex);
for (i = 0; i < BUFFSIZE; i++)
{
printf("%02d ", i);
if (g_buffer[i] == -1)
printf("%s", "null");
else
printf("%d", g_buffer[i]);
if (i == in)
printf("\t<--produce");
printf("\n");
}
printf("%d begin produce product %d\n", num, produce_id);
g_buffer[in] = produce_id;
in = (in + 1) % BUFFSIZE;
printf("%d end produce product %d\n", num, produce_id++);
pthread_mutex_unlock(&g_mutex);
sem_post(&g_sem_empty);
sleep(5);
}
return NULL;
}
int main(void)
{
int i;
for (i = 0; i < BUFFSIZE; i++)
g_buffer[i] = -1;
sem_init(&g_sem_full, 0, BUFFSIZE);
sem_init(&g_sem_empty, 0, 0);
pthread_mutex_init(&g_mutex, NULL);
for (i = 0; i < CONSUMERS_COUNT; i++)
pthread_create(&g_thread[i], NULL, consume, (void *)i);
for (i = 0; i < PRODUCERS_COUNT; i++)
pthread_create(&g_thread[CONSUMERS_COUNT + i], NULL, produce, (void *)i);
for (i = 0; i < CONSUMERS_COUNT + PRODUCERS_COUNT; i++)
pthread_join(g_thread[i], NULL);
sem_destroy(&g_sem_full);
sem_destroy(&g_sem_empty);
pthread_mutex_destroy(&g_mutex);
return 0;
}
与这里的程序相比,程序逻辑没太大变化,只是用pthread_mutex_lock 替代了 sem_mutex,其次这里是演示线程间同步,现在上述程序生产者消费者各一个线程,但生产者睡眠时间是消费者的5倍,故消费者会经常阻塞在sem_wait(&g_sem_empty) 上面,因为缓冲区经常为空,可以将PRODUCTORS_COUNT 改成5,即有5个生产者线程和1个消费者线程,而且生产者睡眠时间还是消费者的5倍,从动态输出可以看出,基本上就动态平衡了,即5个生产者一下子生产了5份东西,消费者1s消费1份,刚好在生产者继续生产前消费完。
四、自旋锁和读写锁简介
(一)、自旋锁
自旋锁类似于互斥锁,它的性能比互斥锁更高。 自旋锁与互斥锁很重要的一个区别在于,线程在申请自旋锁的时候,线程不会被挂起,它处于忙等待的状态,一般用于等待时间比较短的情形。 pthread_spin_init pthread_spin_destroy pthread_spin_lock pthread_spin_unlock
(二)、读写锁
1、只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于读 2、仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配读写锁用于写 3、读写锁用于读称为共享锁,读写锁用于写称为排它锁 pthread_rwlock_init pthread_rwlock_destroy int pthread_rwlock_rdlock int pthread_rwlock_wrlock int pthread_rwlock_unlock
更多有关linux中的锁问题可以参考这篇文章 :《透过Linux内核看无锁编程》
http://www.ibm.com/developerworks/cn/linux/l-cn-lockfree/
参考:
《linux c 编程一站式学习》
《UNP》