第三十九章 POSIX信号量与互斥锁
POSIX信号量相关函数
sem_open
功能:
initialize and open a named semaphore
原型:
sem_t *sem_open(const char *name, int oflag);
参数:
name : 信号量的名字
oflag :
返回值:
成功 : 返回新信号量的地址
失败 : SEM_FAILED errno
Link with -pthread
sem_close
功能:
close a named semaphore
原型:
int sem_close(sem_t *sem);
参数:
sem : 信号量
返回值:
成功 : 0
失败 : -1 errno
Link with -pthread
sem_unlink
功能:
remove a named semaphore
原型:
int sem_unlink(const char *name);
参数:
name : 信号量
返回值:
成功 : 0
失败 : -1 errno
Link with -pthread
sem_init
功能:
initialize an unnamed semaphore
原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem : 信号量
pshared : 指示此信号量是在进程的线程之间还是在进程之间共享。
0 信号量在进程的线程之间共享,并且应位于所有线程可见的某个地址处(例如,全局变量或在堆上动态分配的变量)。
非零 则信号量在进程之间共享,并且应位于共享内存的区域中,任何可以访问共享的进程内存区域可以使用sem_post(3),sem_wait(3)等对信号量进行操作。
value : 初始值
返回值:
成功 : 0
失败 : -1 errno
注意:
虽然初始化的是一个匿名的信号量,它也可以用于不同进程间的多个线程间通信,取决于pshared非0和sem存放在共享内存中
Link with -pthread
sem_destroy
功能:
destroy an unnamed semaphore
原型:
int sem_destroy(sem_t *sem);
参数:
sem : 信号量
返回值:
成功 : 0
失败 : -1 errno
Link with -pthread
sem_wait
功能:
lock a semaphore
原型:
int sem_wait(sem_t *sem);
参数:
sem : 信号量
返回值:
成功 : 0
失败 : -1 errno
Link with -pthread
sem_post
功能:
unlock a semaphore
原型:
int sem_post(sem_t *sem);
参数:
sem : 信号量
返回值:
成功 : 0
失败 : -1 errno
Link with -pthread
POSIX互斥锁相关函数
pthread_mutex_init
功能:
initialize a mutex
原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
参数:
mutex : 互斥锁
attr : 指定了新建互斥锁的属性。如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁
返回值:
成功 : 0
失败 : 返回对应的错误码
pthread_mutex_lock
功能:
lock a mutex
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
mutex : 互斥锁
返回值:
成功 : 0
失败 : 返回对应的错误码
pthread_mutex_unlock
功能:
lock a mutex
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
mutex : 互斥锁
返回值:
成功 : 0
失败 : 返回对应的错误码
pthread_mutex_destroy
功能:
destroy a mutex
原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex : 互斥锁
返回值:
成功 : 0
失败 : 返回对应的错误码
生产者消费者问题
pctest.c
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
#define CONSUMERS_COUNT 1 //消费者线程的个数
#define PRODUCERS_COUNT 5 //生产者线程的个数
#define BUFFSIZE 10 //缓冲区的大小
int g_buffer[BUFFSIZE]; //存放产品的ID的缓冲区
unsigned short in = 0; //产品的生产位置
unsigned short out = 0; //产品的消费位置
unsigned short produce_id = 0; //当前正在生产的产品的id
unsigned short consume_id = 0; //当前正在消费的产品的id
sem_t g_sem_full; //buffer满的信号量
sem_t g_sem_empty; //buffer空的信号量
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 waiting 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 producet %d \n", num, consume_id);
g_buffer[out] = -1;
out = (out + 1) % BUFFSIZE;
printf("%d end consume producet %d \n", num, consume_id++);
pthread_mutex_unlock(&g_mutex);
sem_post(&g_sem_full);
sleep(1);
}
return NULL;
}
void *produce(void *arg)
{
int i;
int num = (int)arg;
while (1)
{
printf("%d waiting 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 producet %d \n", num, produce_id);
g_buffer[in] = produce_id;
in = (in + 1) % BUFFSIZE;
printf("%d end produce producet %d \n", num, produce_id++);
pthread_mutex_unlock(&g_mutex);
sem_post(&g_sem_empty);
sleep(5);
}
return NULL;
}
int main()
{
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_spin_init
功能:
initialize a spin lock object
原型:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
参数:
lock : 自旋锁
pshared :
PTHREAD_PROCESS_SHARED 允许任何有权访问分配了旋转锁的内存的线程对旋转锁进行操作,即使已分配该旋转锁在多个进程共享的内存中
PTHREAD_PROCESS_PRIVATE 只能由在与初始化旋转锁的线程相同的进程中创建的线程操作
返回值:
成功 : 0
失败 : 返回错误编号
pthread_spin_destroy
功能:
destroy a spin lock object
原型:
int pthread_spin_destroy(pthread_spinlock_t *lock);
参数:
lock : 自旋锁
返回值:
成功 : 0
失败 : 返回错误编号
pthread_spin_lock
功能:
lock a spin lock object
原型:
int pthread_spin_destroy(pthread_spinlock_t *lock);
参数:
lock : 自旋锁
返回值:
成功 : 0
失败 : errno
pthread_spin_unlock
功能:
unlock a spin lock object
原型:
int pthread_spin_unlock(pthread_spinlock_t *lock);
参数:
lock : 自旋锁
返回值:
成功 : 0
失败 : errno
spinlockvsmutex1.cc
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <sys/time.h>
#include <list>
#include <pthread.h>
#define LOOPS 50000000
using namespace std;
list<int> the_list;
#ifdef USE_SPINLOCK
pthread_spinlock_t spinlock;
#else
pthread_mutex_t mutex;
#endif
//Get the thread id
pid_t gettid() { return syscall( __NR_gettid ); }
void *consumer(void *ptr)
{
int i;
printf("Consumer TID %lun", (unsigned long)gettid());
while (1)
{
#ifdef USE_SPINLOCK
pthread_spin_lock(&spinlock);
#else
pthread_mutex_lock(&mutex);
#endif
if (the_list.empty())
{
#ifdef USE_SPINLOCK
pthread_spin_unlock(&spinlock);
#else
pthread_mutex_unlock(&mutex);
#endif
break;
}
i = the_list.front();
the_list.pop_front();
#ifdef USE_SPINLOCK
pthread_spin_unlock(&spinlock);
#else
pthread_mutex_unlock(&mutex);
#endif
}
return NULL;
}
int main()
{
int i;
pthread_t thr1, thr2;
struct timeval tv1, tv2;
#ifdef USE_SPINLOCK
pthread_spin_init(&spinlock, 0);
#else
pthread_mutex_init(&mutex, NULL);
#endif
// Creating the list content...
for (i = 0; i < LOOPS; i++)
the_list.push_back(i);
// Measuring time before starting the threads...
gettimeofday(&tv1, NULL);
pthread_create(&thr1, NULL, consumer, NULL);
pthread_create(&thr2, NULL, consumer, NULL);
pthread_join(thr1, NULL);
pthread_join(thr2, NULL);
// Measuring time after threads finished...
gettimeofday(&tv2, NULL);
if (tv1.tv_usec > tv2.tv_usec)
{
tv2.tv_sec--;
tv2.tv_usec += 1000000;
}
printf("Result - %ld.%ldn", tv2.tv_sec - tv1.tv_sec,
tv2.tv_usec - tv1.tv_usec);
#ifdef USE_SPINLOCK
pthread_spin_destroy(&spinlock);
#else
pthread_mutex_destroy(&mutex);
#endif
return 0;
}
svm2.cc
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/syscall.h>
#define THREAD_NUM 2
pthread_t g_thread[THREAD_NUM];
#ifdef USE_SPINLOCK
pthread_spinlock_t g_spin;
#else
pthread_mutex_t g_mutex;
#endif
__uint64_t g_count;
pid_t gettid()
{
return syscall(SYS_gettid);
}
void *run_amuck(void *arg)
{
int i, j;
printf("Thread %lu started.n", (unsigned long)gettid());
for (i = 0; i < 10000; i++) {
#ifdef USE_SPINLOCK
pthread_spin_lock(&g_spin);
#else
pthread_mutex_lock(&g_mutex);
#endif
for (j = 0; j < 100000; j++) {
if (g_count++ == 123456789)
printf("Thread %lu wins!n", (unsigned long)gettid());
}
#ifdef USE_SPINLOCK
pthread_spin_unlock(&g_spin);
#else
pthread_mutex_unlock(&g_mutex);
#endif
}
printf("Thread %lu finished!n", (unsigned long)gettid());
return (NULL);
}
int main(int argc, char *argv[])
{
int i, threads = THREAD_NUM;
printf("Creating %d threads...n", threads);
#ifdef USE_SPINLOCK
pthread_spin_init(&g_spin, 0);
#else
pthread_mutex_init(&g_mutex, NULL);
#endif
for (i = 0; i < threads; i++)
pthread_create(&g_thread[i], NULL, run_amuck, (void *) i);
for (i = 0; i < threads; i++)
pthread_join(g_thread[i], NULL);
printf("Done.n");
return (0);
}
总结
-
Mutex适合对锁操作非常频繁的场景,并且具有更好的适应性。尽管相比spin lock它会花费更多的开销(主要是上下文切换),但是它能适合实际开发中复杂的应用场景,在保证一定性能的前提下提供更大的灵活度。
-
spin lock的lock/unlock性能更好(花费更少的cpu指令),但是它只适应用于临界区运行时间很短的场景。而在实际软件开发中,除非程序员对自己的程序的锁操作行为非常的了解,否则使用spin lock不是一个好主意(通常一个多线程程序中对锁的操作有数以万次,如果失败的锁操作(contended lock requests)过多的话就会浪费很多的时间进行空等待)。
-
更保险的方法或许是先(保守的)使用 Mutex,然后如果对性能还有进一步的需求,可以尝试使用spin lock进行调优。毕竟我们的程序不像Linux kernel那样对性能需求那么高(Linux Kernel最常用的锁操作是spin lock和rw lock)。
读写锁
- 只要没有线程持有给定的读写锁用于写,那么任意数目的线程可以持有读写锁用于读
- 仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配读写锁用于写
- 读写锁用于读 称为共享锁, 读写锁用于写 称为排它锁
pthread_rwlock_init
功能:
initialize a read-write lock object
原型:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
参数:
rwlock : 读写锁指针
attr : 读写锁属性指针
返回值:
成功 : 0
失败 : 返回错误码
pthread_rwlock_destroy
功能:
destroy a read-write lock object
原型:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
rwlock : 读写锁指针
返回值:
成功 : 0
失败 : 返回错误码
pthread_rwlock_rdlock
功能:
lock a read-write lock object for reading
原型:
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
参数:
rwlock : 读写锁指针
返回值:
成功 : 0
失败 : 返回错误码
pthread_rwlock_wrlock
功能:
lock a read-write lock object for writing
原型:
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
参数:
rwlock : 读写锁指针
返回值:
成功 : 0
失败 : 返回错误码
pthread_rwlock_unlock
功能:
unlock a read-write lock object
原型:
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
参数:
rwlock : 读写锁指针
返回值:
成功 : 0
失败 : 返回错误码
rw_test.cc
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
using namespace std;
struct{
pthread_rwlock_t rwlock;
int product;
}sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0};
void * produce(void *ptr)
{
for (int i = 0; i < 5; ++i)
{
pthread_rwlock_wrlock(&sharedData.rwlock);
sharedData.product = i;
pthread_rwlock_unlock(&sharedData.rwlock);
sleep(1);
}
}
void * consume1(void *ptr)
{
for (int i = 0; i < 5;)
{
pthread_rwlock_rdlock(&sharedData.rwlock);
cout<<"consume1:"<<sharedData.product<<endl;
pthread_rwlock_unlock(&sharedData.rwlock);
++i;
sleep(1);
}
}
void * consume2(void *ptr)
{
for (int i = 0; i < 5;)
{
pthread_rwlock_rdlock(&sharedData.rwlock);
cout<<"consume2:"<<sharedData.product<<endl;
pthread_rwlock_unlock(&sharedData.rwlock);
++i;
sleep(1);
}
}
int main()
{
pthread_t tid1, tid2, tid3;
pthread_create(&tid1, NULL, produce, NULL);
pthread_create(&tid2, NULL, consume1, NULL);
pthread_create(&tid3, NULL, consume2, NULL);
void *retVal;
pthread_join(tid1, &retVal);
pthread_join(tid2, &retVal);
pthread_join(tid3, &retVal);
return 0;
}