【linux】linux下多种锁的比较 和 实现单实例的方法
线程同步的各种方法。包括:
- 互斥量(mutex)
- 读写锁
- 条件变量
- 信号量
- 文件互斥
在linux内核中,有很多同步机制。比较经典的有
原子操作
spin_lock(忙等待的锁)
mutex(互斥锁)
semaphore(信号量)
互斥量(mutex)
创建互斥锁
静态方式
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
POSIX定义了一个结构常量宏PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁
动态方式
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
*mutexattr)
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
注销互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex)
锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex) //加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex) //解锁
int pthread_mutex_trylock(pthread_mutex_t *mutex) //测试加锁
pthread_mutex_trylock() 语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回 EBUSY而不是挂起等待。
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing
pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; // it's mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; //it's mean writer can writing
int readCount = 0;
void *reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);
pthread_mutex_lock(&rd);
readCount++;
if( readCount == 1)
{
pthread_mutex_lock(&wr);
}
pthread_mutex_unlock(&rd);
printf("reader %d is reading\n", id);
sleep( rand() % 3);
pthread_mutex_lock(&rd);
readCount--;
if (readCount == 0)
{
pthread_mutex_unlock(&wr);
}
pthread_mutex_unlock(&rd);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}
void *writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 3);
pthread_mutex_lock(&wr);
printf("\twriter is writing\n");
sleep( rand() % 3);
pthread_mutex_unlock(&wr);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");
}
int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writerTid;
int i;
err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can't create process for writer");
}
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i + 1));
if (err != 0)
{
err_quit("can't create process for reader");
}
}
pause();
return 0;
}
g++ reader_writer.c -l pthread -o a.out
互斥量实现读者写者问题(写者优先)
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing
pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; // it's mean reader can reading
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; // it's mean writer can writing
pthread_mutex_t priority = PTHREAD_MUTEX_INITIALIZER; // it's mean writer can writing
int readCount = 0;
void* reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);
pthread_mutex_lock(&priority);
pthread_mutex_lock(&rd);
readCount++;
if( readCount == 1)
{ //first reader
pthread_mutex_lock(&wr);
}
pthread_mutex_unlock(&rd);
pthread_mutex_unlock(&priority);
printf("reader %d is reading\n", id);
sleep( rand() % 3);
pthread_mutex_lock(&rd);
readCount--;
if (readCount == 0)
{ //last reader
pthread_mutex_unlock(&wr);
}
pthread_mutex_unlock(&rd);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}
void* writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 4);
pthread_mutex_lock(&priority);
pthread_mutex_lock(&wr);
printf("\twriter is writing\n");
sleep( rand() % 4);
pthread_mutex_unlock(&wr);
pthread_mutex_unlock(&priority);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");
}
int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writer_tid;
int i;
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
if (err != 0)
{
err_quit("can't create process for reader");
}
}
err = pthread_create(&writer_tid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can't create process for writer");
}
pause();
return 0;
}
读写锁
读写锁适合于对数据结构的读次数比写次数多得多的情况.因为,读锁时可以共享,写锁时独占,所以读写锁又叫共享-独占锁.
初始化和销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const
pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功则返回0,出错则返回错误编号. 同互斥量以上,在释放读写锁占用的内存之前,需要先通过 pthread_rwlock_destroy对读写锁进行清理工作, 释放由init分配的资源.
读和写
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功则返回0,出错则返回错误编号.这3个函数分别实现获取读锁,获取写锁和释放锁的操作.
非阻塞版
获取锁的两个函数是阻塞操作,同样,非阻塞的函数为:
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
成功则返回0,出错则返回错误编号.非阻塞的获取锁操作,如果可以获取则返回0,否则返回 错误的EBUSY.
#include <pthread.h>
#include <signal.h>
#include "apue.h"
#define N 5 //No. of reader
#define M 5 //No. of reading and writing
pthread_rwlock_t lock; //it's mean writer can writing
int readCount = 0;
void* reader(void *arg)
{
int n = M;
int id = (int)arg;
while (n--)
{
sleep( rand() % 3);
pthread_rwlock_rdlock(&lock);
printf("reader %d is reading\n", id);
sleep( rand() % 3);
pthread_rwlock_unlock(&lock);
printf("reader %d is leaving\n", id);
}
printf("----reader %d has done----\n", (int)arg);
}
void* writer(void *arg)
{
int n = M;
while (n--)
{
sleep( rand() % 3);
pthread_rwlock_wrlock(&lock);
printf("\twriter is writing\n");
sleep( rand() % 3);
pthread_rwlock_unlock(&lock);
printf("\twriter is leaving\n");
}
printf("----writer has done----\n");
}
int main(int argc, const char *argv[])
{
int err;
pthread_t tid[N], writerTid;
int i;
err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
if (err != 0)
{
err_quit("can't create process for writer");
}
pthread_rwlock_init(&lock, NULL);
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, reader, (void *)(i+1));
if (err != 0)
{
err_quit("can't create process for reader");
}
}
pause();
pthread_rwlock_destroy(&lock);
return 0;
}
条件变量
与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使 "条件成立"(给出条件成立信号)。
条件的检测是在互斥锁的保护下进行的。
如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多 个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量 可以被用来实现这两进程间的线程同步。
条件变量分为两部分:条件和变量.
条件本身是由互斥量保护的.线程在改变条件状态前先要锁住 互斥量.它利用线程间共享的全局变量进行同步的一种机制。
相关的函数如下:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//动态初始化
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const
timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞
简要说明:
初始化:
条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//动态初始化
pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER; //静态初始化
动态初始化,释放动态条件变量的内存空间之前, 要用 pthread_cond_destroy对其进行清理.
成功则返回0, 出错则返回错误编号.
当pthread_cond_init的attr参数为NULL时,会创建一个默认属性的条件变量;非默认情况以后讨论.
等待条件:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t
*restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t
*restrict mutex, const struct timespec *restrict timeout);
成功则返回0,出错则返回错误编号.这两个函数分别是阻塞等待和超时等待.
通知条件
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);
成功则返回0, 出错则返回错误编号.
这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread1(void *arg)
{
pthread_cleanup_push(pthread_mutex_unlock, &mutex);
//提供函数回调保护
while (1) {
printf("thread1 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("thread1 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(4);
}
pthread_cleanup_pop(0);
}
void *thread2(void *arg)
{
while (1) {
printf("thread2 is running\n");
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
printf("thread2 applied the condition\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
pthread_t thid1, thid2;
printf("condition variable study!\n");
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&thid1, NULL, (void *) thread1, NULL);
pthread_create(&thid2, NULL, (void *) thread2, NULL);
do {
pthread_cond_signal(&cond);
} while (1);
sleep(20);
pthread_exit(0);
return 0;
}
信号量
信号量的具体使用方法,请
man sem_init
相关的几个系统调用:
sem_init
sem_wait
sem_trywait
sem_post
sem_getvalue
sem_destory
下面来看一个信号量解决哲学家就餐问题,在这里semaphore 初始为1,用法和互斥量没有什么区别。
#include <semaphore.h>
#include <pthread.h>
#include "apue.h"
#define N 5 // No. of philosopher
#define M 5 // times of eating
sem_t forks[N];
void * thr_philosopher( void *arg);
int main(int argc, char* argv[])
{
int i = 0;
int err;
pthread_t tid[N];
void *tret;
//initilize semaphore
for (i = 0; i < N; i++)
{
if(sem_init(&forks[i], 0, 1) != 0)
{
err_quit("init forks error");
}
}
//create thread
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, thr_philosopher, (void *)i);
if (err != 0)
{
err_quit("can't create thread %d: %s\n", i + 1, strerror(err));
}
}
//get the return value
for (i = 0; i < N; i++)
{
err = pthread_join(tid[i], &tret);
if (err != 0)
{
err_quit("can't join with philosopher %d : %s\n", i + 1,
strerror(err));
}
printf("-------------------philosopher %d has done-------------------\n", (int)tret);
}
// delete the source of semaphore
for (i = 0; i < N; i++)
{
err = sem_destroy(&forks[i]);
if (err != 0)
{
err_sys("can't destory semaphore");
}
}
exit(0);
}
void * thr_philosopher( void *arg)
{
/*
* here cann't judge arg == NULL
* because (void *)0 will lead to arg = NULL
*/
int n = M;
int i = 0;
i = (int)arg;
while ( n-- )
{
sleep(1);
if ( i == N - 1)
{
sem_wait(&forks[0]);
sem_wait(&forks[i]);
}
else
{
sem_wait(&forks[i]);
sem_wait(&forks[i + 1]);
}
printf("philosopher %d is eating\n", i + 1);
if ( i == N - 1)
{
sem_post(&forks[0]);
sem_post(&forks[i]);
}
else
{
sem_post(&forks[i]);
sem_post(&forks[i + 1]);
}
}
return ((void*)i);
}
文件互斥
文件互斥并不是操作系统提供的一组API,而是使用了一点小技巧,我们可以通过linux下文件互斥地打开,实现线程/进程互斥的访问资源,以此实现多线程编程。
值得注意的是,文件互斥的方式不但适用于多线程编程,还能实现多进程之间的交互。
文件互斥还有一个妙用--保证一个系统中只有一个实例,请参考这里。
新建lock.h 文件,内容入下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void initlock(const char *lockfile);
void lock(const char *lockfile);
void unlock(const char *lockfile);
新建lock.c 文件,内容入下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void initlock(const char *lockfile)
{
int i;
unlink(lockfile);
}
void lock(const char *lockfile)
{
int fd;
while ( (fd = open(lockfile, O_RDONLY | O_CREAT | O_EXCL)) < 0)
sleep(1);
close(fd);
}
void unlock(const char *lockfile)
{
unlink(lockfile);
}
新建main.c 文件,内容如下:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
#include "lock.h"
#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};
void takeFork( int i )
{
if ( i == N - 1 )
{
lock(forks[0]);
printf("philosopher %d: takefork 0\n", i + 1 );
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
}
else
{
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
lock(forks[i+1]);
printf("philosopher %d: takefork %d\n", i + 1, i+1 );
}
}
void eating(int i, int nsecs)
{
printf("\tphilosopher %d: eat %d s\n", i + 1, nsecs );
sleep(nsecs);
}
void thinking(int i, int nsecs)
{
printf("philosopher %d: think %d sec\n", i + 1, nsecs );
sleep(nsecs);
}
void putFork( int i )
{
if ( i == N - 1 )
{
unlock(forks[0]);
unlock(forks[i]);
}
else
{
unlock(forks[i]);
unlock(forks[i+1]);
}
}
void* philosopher(void *arg)
{
int i = (int)arg;
// printf("philosopher %d : pid = %d is running", i + 1, getpid());
int m = M;
int nsecs;
srand( time(0) );
while ( m-- )
{
nsecs = rand() % 5;
thinking(i, nsecs);
takeFork(i);
nsecs = rand() % 5;
eating(i, nsecs);
putFork(i);
}
printf("====================philosopher %d : is\
successful===================\n", i + 1 );
}
int main(int argc, char* argv[])
{
int i;
int err;
pthread_t tid[N];
for (i = 0; i < sizeof(forks) / sizeof(forks[0]); i++)
{
initlock(forks[i]);
}
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, philosopher, (void *)i);
if (err != 0)
{
err_quit("can't create process for philosopher");
}
}
pause();
}
最后,我们来看一个使用文件互斥实现进程之间的交互的例子,其中,lock.h 和 lock.c 请参考上面的实现。main.c 的实现如下:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "apue.h"
#include "lock.h"
#define N 5 // No. of philosopher
#define M 3 // No. of eating
static char* forks[] = {"fork0", "fork1", "fork2", "fork3", "fork4"};
void takeFork( int i )
{
if ( i == N - 1 )
{
lock(forks[0]);
printf("philosopher %d: takefork 0\n", i + 1 );
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
}
else
{
lock(forks[i]);
printf("philosopher %d: takefork %d\n", i + 1, i );
lock(forks[i+1]);
printf("philosopher %d: takefork %d\n", i + 1, i+1 );
}
}
void eating(int i, int nsecs)
{
printf("\tphilosopher %d: eat %d s\n", i + 1, nsecs );
sleep(nsecs);
}
void thinking(int i, int nsecs)
{
printf("philosopher %d: think %d sec\n", i + 1, nsecs );
sleep(nsecs);
}
void putFork( int i )
{
if ( i == N - 1 )
{
unlock(forks[0]);
unlock(forks[i]);
}
else
{
unlock(forks[i]);
unlock(forks[i+1]);
}
}
void* philosopher(void *arg)
{
int i = (int)arg;
// printf("philosopher %d : pid = %d is running", i + 1, getpid());
int m = M;
int nsecs;
srand( time(0) );
while ( m-- )
{
nsecs = rand() % 5;
thinking(i, nsecs);
takeFork(i);
nsecs = rand() % 5;
eating(i, nsecs);
putFork(i);
}
printf("====================philosopher %d : is\
successful===================\n", i + 1 );
}
int main(int argc, char* argv[])
{
int i;
int err;
pthread_t tid[N];
for (i = 0; i < sizeof(forks) / sizeof(forks[0]); i++)
{
initlock(forks[i]);
}
for (i = 0; i < N; i++)
{
err = pthread_create(&tid[i], NULL, philosopher, (void *)i);
if (err != 0)
{
err_quit("can't create process for philosopher");
}
}
pause();
}
如果你是新手,可能对下面这几行代码不是很理解,请参考酷壳网的《一个fork的面试题》。
for ( i = 0; i < N; i++ )
{
pid = fork();
if (pid == 0 || pid == -1)
{
break;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)