为了保证临界资源的安全性和可靠性,线程不得不使用锁,同一时间只允许一个或几个线程访问变量。常用的锁有互斥量,读写锁,条件变量
一、互斥量
互斥量是用pthread_mutex_t数据类型表示的,在使用之前,必须对其进行初始化,可以把它设置为PTHREAD_MUTEX_INITIALIZER(只适于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化,最后还要调用pthread_mutex_destroy进行释放。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

要用默认的属性初始化互斥量,只需把attr设为NULL,后面在讨论互斥量属性。
对互斥量进行加锁,使用pthread_mutex_lock,如果互斥量已经上锁,调用线程将阻塞至互斥量解锁,对互斥量解锁,使用pthread_mutex_unlock,如果线程不希望被阻塞,它可以调用pthread_mutex_trylock尝试对互斥量进行加锁,如果互斥量未锁住,则成功加锁,如果互斥量已锁住,pthread_mutex_trylock就会失败,返回EBUSY。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

例子:

#include <stdio.h>
#include <pthread.h>struct foo
{int f_count;pthread_mutex_t f_lock;int f_id;
};struct foo * foo_alloc(int id)
{struct foo *fp = NULL;if ((fp = malloc(sizeof(struct foo))) != NULL){fp->f_count = 1;fp->f_id = id;if (pthread_mutex_init(&fp->f_lock, NULL) != 0){free(fp);return NULL;}}return fp;
}void foo_hold(struct foo *fp)
{pthread_mutex_lock(&fp->f_lock);fp->f_count++;pthread_mutex_unlock(&fp->f_lock);
}void foo_rele(struct foo *fp)
{pthread_mutex_lock(&fp->f_lock);if (--fp->f_count == 0){pthread_mutex_unlock(&fp->f_lock);pthread_mutex_destroy(&fp->f_lock);free(fp);}else{pthread_mutex_unlock(&fp->f_lock);}
}

View Code

上面的例子描述了用于保护某个数据结构的互斥量,我们在对象中嵌入引用计数,确保在所有使用该对象的线程完成数据访问之前,该对象的内存空间不会被释放。
如果线程对同一个互斥量加锁两次,那么它自身将陷入死锁状态。如果有一个以上的互斥量,且允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,也阻塞,就死锁了。
可以通过仔细控制互斥量加锁的顺序来避免死锁的发生,譬如要求所有线程必须先锁住互斥量A才能锁住互斥量B。另一种办法是当线程无法获得下一个互斥量的时候,就释放自己已占有的互斥量,过一段时间再试。
例子:

#include "apue.h"
#include <pthread.h>#define NMASH 29
#define HASH(id) (((unsigned long)id) % NMASH)struct foo *fh[NMASH];pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;struct foo
{int f_count;pthread_mutex_t f_lock;int f_id;struct foo *f_next;
};struct foo *foo_alloc(int id)
{struct foo *fp = NULL;int idx = 0;if ((fp = malloc(sizeof(struct foo))) != NULL){fp->f_count = 1;fp->f_id = if;if (pthread_mutex_init(&fp->f_lock, NULL) != 0){free(fp);return NULL;}idx = HASH(id);pthread_mutex_lock(&hashlock);fp->f_next = fh[idx];fh[idx] = fp;pthread_mutex_lock(&fp->f_lock);pthread_mutex_unlock(&hashlock);pthread_mutex_unlock(&fp->f_lock);}return fp;
}void foo_hold(struct foo *fp)
{pthread_mutex_lock(&fp->f_lock);fp->f_count++;pthread_mutex_unlock(&fp->f_lock);
}struct foo *foo_find(int id)
{struct foo *fp = NULL;pthread_mutex_lock(&hashlock);for (fp = fh[HASH(id)]; fp != NULL; fp = fp->next){if (fp->f_id = id){foo_hold(fp);break;}}pthread_mutex_unlock(&hashlock);return fp;
}void foo_rele(struct foo *fp)
{struct foo *tfp = NULL;int idx = 0;pthread_mutex_lock(&fp->f_lock);if (fp->f_count == 1){pthread_mutex_unlock(&fp->f_lock);pthread_mutex_lock(&hashlock);pthread_mutex_lock(&fp->f_lock);if (fp->f_count != 1){fp->f_count--;pthread_mutex_unlock(&hashlock);pthread_mutex_unlock(&fp->f_lock);return;}idx = HASH(fp->f_id);tfp = fh[idx];if (tfp = fp){fh[idx] = fp->f_next}else{while(tfp->next != fp){tfp = tfp->next;}tfp->next = fp->f_next;}pthread_mutex_unlock(&hashlock);pthread_mutex_unlock(&fp->f_lock);pthread_mutex_destroy(&fp->f_lock);free(fp);}else{fp->f_count--;pthread_mutex_unlock(&fp->f_lock);}
}

View Code

这个例子比上一个例子多了一个散列表和一个保护散列表的互斥量,加锁的顺序是先hashlock,再f_lock,注意这个顺序,就不会发生死锁,不过这样也导致代码太繁琐,最后一个函数解锁f_lock后重新加锁f_lock,需要重新考察f_count的值,因为可能在这期间被其他线程修改。
这样的方式太复杂,让hashlock也保护f_cout,事情会简单很多。
例子:

#include "apue.h"
#include <pthread.h>#define NMASH 29
#define HASH(id) (((unsigned long)id) % NMASH)struct foo *fh[NMASH];pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;struct foo
{int f_count;pthread_mutex_t f_lock;int f_id;struct foo *f_next;
};struct foo *foo_alloc(int id)
{struct foo *fp = NULL;int idx = 0;if ((fp = malloc(sizeof(struct foo))) != NULL){fp->f_count = 1;fp->f_id = if;if (pthread_mutex_init(&fp->f_lock, NULL) != 0){free(fp);return NULL;}idx = HASH(id);pthread_mutex_lock(&hashlock);fp->f_next = fh[idx];fh[idx] = fp;pthread_mutex_lock(&fp->f_lock);pthread_mutex_unlock(&hashlock);pthread_mutex_unlock(&fp->f_lock);}return fp;
}void foo_hold(struct foo *fp)
{pthread_mutex_lock(&hashlock);fp->f_count++;pthread_mutex_unlock(&hashlock);
}struct foo *foo_find(int id)
{struct foo *fp = NULL;pthread_mutex_lock(&hashlock);for (fp = fh[HASH(id)]; fp != NULL; fp = fp->next){if (fp->f_id = id){foo_hold(fp);break;}}pthread_mutex_unlock(&hashlock);return fp;
}void foo_rele(struct foo *fp)
{struct foo *tfp = NULL;int idx = 0;pthread_mutex_lock(&hashlock);if (fp->f_count == 1){idx = HASH(fp->f_id);tfp = fh[idx];if (tfp = fp){fh[idx] = fp->f_next}else{while(tfp->next != fp){tfp = tfp->next;}tfp->next = fp->f_next;}pthread_mutex_unlock(&hashlock);pthread_mutex_destroy(&fp->f_lock);free(fp);}else{fp->f_count--;pthread_mutex_unlock(&hashlock);}
}

View Code

当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock和pthread_mutex_lock是基本等价的,但是达到超时时间后,pthread_mutex_timedlock会返回。超时时间指原意等待的绝对时间。这个超时时间是用timespec来表示的
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr);

二、读写锁
读写锁与互斥量相似,不过读写锁允许更高的并行性,一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁,简单地来说,就说支持一个写者,多个读者。
当读写锁是写加锁状态时,所以试图对这个锁加锁的线程都会被阻塞,当读写锁在读加锁状态时,所以试图以读模式对它进行加锁的线程都可以得到访问权,但是希望以写模式加锁的线程会被阻塞。不过当有一个线程企图以写模式获取锁时,读写锁会阻塞后面的读模式锁请求,防止读模式锁长期占用。
可知,读写锁适用于对数据结构读的次数远大于写的情况,又称共享互斥锁,读共享,写互斥。
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

读写锁调用phtread_rwlock_init进行初始化,如果希望读写锁有默认的属性,传null给attr即可。
读的模式下锁定读写锁,需要调用phtread_rwlock_rdlock,写的模式下锁定读写锁,需要调用pthread_rwlock_wrlock,不过以何种方式锁定读写锁,都可以调用pthread_rwlock_unlock解锁。
#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);

例子:

#include <stdio.h>
#include <pthread.h>struct job
{struct job *j_next;struct job *j_prev;pthread_t j_id;
};struct queue
{struct job *q_head;struct job *q_tail;pthread_rwlock_t q_lock;
};int queue_init(struct queue *qp)
{int err;qp->q_head = NULL;qp->q_tail = NULL;err = pthread_rwlock_init(&qb->q_lock, NULL);if (err != 0){return err;}return 0
}void job_insert(struct queue *qp, struct job *jp)
{pthread_rwlock_wrlock(&qb->q_lock);jp->next = qp->head;jp->j_prev = NULL;if (qp->q_head != NULL){qp->q_head->j_prev = jp;}else{qp->tail = jp;}qp->head = jp;pthread_rwlock_unlock(&qp->q_lock);
}void job_append(struct queue *qp, struct job *jp)
{pthread_rwlock_wrlock(&qp->q_lock);jp->j_next = NULL;jp->j_prev = qp->tail;if (qp->q_tail != NULL){qp->q_tail->j_next = jp;}qp->q_tail = jp;pthread_rwlock_unlock(&qp->q_lock);
}void job_remove(struct queue *qp, struct job *jp)
{pthread_rwlock_wrlock(&qp->q_lock);if (jp == qp->q_head){qp->q_head = jp->j_next;if (qp->q_tail == jp){qp->tail = NULL;}else{jp->next->j_prev = jp->j_prev;}}else if (jp == qp->q_tail){qp->q_tail = jp->j_prev;jp->j_prev->j_next = NULL;}else{jp->j_prev->j_next = jp->j_next;jp->j_next->j_prev = jp->j_prev;}pthread_rwlock_unlock(&qp->q_lock);
}struct job *job_find(struct queue *qp, pthread_t id)
{struct job *jp;if (pthread_rwlock_rdlock(&qp->q_lock) != 0){return NULL;}for (jp = qb->q_head; jp != NULL; jp = jp->j_next){if (pthread_equal(jp->j_id, id)){break;}}pthread_rwlock_unlock(&qp->q_lock);return jp;
}

View Code

与互斥量一样,读写锁也有带超时的读写锁函数,避免陷入永久的阻塞。
#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict tsptr);

三、条件变量
条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身由互斥量保护,线程在改变条件状态之前必须锁定互斥量。在使用条件变量之前,必须把它初始化,可以把常量PTHREAD_CON_INITIALIZE赋给静态分配的条件变量,也可用pthread_cond_init函数进行初始化。使用pthread_cond_destroy释放。
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_con_t *cond);

如果需要一个默认属性的条件变量,把null给attr即可。
我们使用pthread_cond_wait等待条件变量为真,如果在给定时间内不能满足,则返回错误码。
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex)
int pthread_cond_timedwait(pthread_cond_t *restrict cond, phtread_mutex_t *restrict mutex, const struct timespec *restrict tsptr)

调用者把锁定的互斥量传给函数,函数自动把调用线程放到等待条件的线程列表上,对互斥量解锁,当pthread_cond_wait返回时,互斥量再次被锁住。pthread_cond_timedwait多了原意等待的时间。
有两个函数可用于通知线程条件已满足,pthread_cond_signal函数至少唤醒一个,pthread_cond_broadcast唤醒等待该条件的所有线程。
#include<phtread.h>
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)

例子:

#include <pthread.h>struct msg
{struct msg *m_next;
};struct msg *workq;pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;void process_msg(void)
{struct msg *mp;for(;;){pthread_mutex_lock(&qlock);while (workq == NULL){pthread_cond_wait(&qready, &qlock);}mp = workq;workq = mp->m_next;pthread_mutex_unlock(&qlock);}
}void enqueue_msg(struct msg *mp)
{pthread_mutex_lock(&qlock);mp->m_next = workq;workq = mp;pthread_mutex_unlock(&qlock);pthread_cond_signal(&qready);
}

View Code

转载于:https://www.cnblogs.com/shenlinken/p/5778564.html

APUE学习之多线程编程(二):线程同步相关推荐

  1. Win32多线程编程(3) — 线程同步与通信

    一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线程的 ...

  2. 多线程编程、线程同步|安全和线程通信

    多线程编程 多线程的优势 线程在程序中是独立的.并发的执行流,与分隔的进程相比,进程中的线程之间的隔离程度要小.他们共享内存.文件句柄和其他每个进程应有的状态. 因为线程的划分尺度小于进程,使得多线程 ...

  3. java多线程同步与死锁,廖雪峰Java11多线程编程-2线程同步-3死锁

    在多线程编程中,要执行synchronized块,必须首先获得指定对象的锁. 1.Java的线程锁是可重入的锁 public void add(int m){ synchronized (lock){ ...

  4. VC++中多线程学习(MFC多线程)三(线程同步包含:原子互锁、关键代码段、互斥器Mutex、Semaphores(信号量)、Event Objects(事件))

    目录 ​​​​​​​​​​​​ 线程同步的必要性: 2.解决同步问题的方法 2.1原子互锁家族函数 2.2Critical Sections(关键代码段.关键区域.临界区域) 2.3 互斥器Mutex ...

  5. iOS多线程编程:线程同步总结 NSCondtion

    1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一 ...

  6. 多线程编程之线程同步主要函数一览

    互斥锁 int pthread_mutex_init(pthread_mutex_t *restrict, const pthread_mutexattr_t *restrict); int pthr ...

  7. 多线程编程之三——线程间通讯

    七.线程间通讯 一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信.这种线程间的通信不但是难以避免的,而 ...

  8. python学习笔记——多线程编程

    python学习笔记--多线程编程 基础不必多讲,还是直接进入python. Python代码代码的执行由python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时 ...

  9. 【转】1.3异步编程:线程同步基元对象

    开始<异步编程:同步基元对象(上)> 示例:异步编程:线程同步基元对象.rar 如今的应用程序越来越复杂,我们常常需要多线程技术来提高我们应用程序的响应速度.每个线程都由自己的线程ID,当 ...

最新文章

  1. 解释一下c语言 for(;;) printf(*);,printf()函数的一个问题
  2. platform下的js分析_1
  3. 使用参数化SQL语句进行模糊查找
  4. 基于Netty的http服务器
  5. hadoop Connection refused: no further information原因排查(Centos7)
  6. Python入门5_条件循环语句
  7. 怎样更改计算机ip用户名,无法修改系统IP地址
  8. 机器学习入门——线性回归详细分析
  9. vant-list上拉加载onload事件触发多次
  10. SCAU 正n多边形类的定义与使用
  11. 免费好用的图片压缩网站,有这五个就够了(收藏备用)
  12. linux环境vmd安装,Ubuntu下VMD安装
  13. 3D动画制作流程概要
  14. 计算机班级学生分析,【本班学生学业成绩状况】_本班学生情况分析报告
  15. APP 的开发费用标准是什么?
  16. Photoshop学习(二):换色
  17. 矩阵中的旋转(Rotation)
  18. B - 最少硬币问题
  19. 云计算实验2 Spark分布式内存计算框架配置及编程案例
  20. 解决Xposed不联网问题,附带Xposed安装教程雷电夜神逍遥模拟器

热门文章

  1. JVM调优:定位垃圾的常用算法
  2. 【视频】vue表单提交
  3. Hystrix默认超时时间
  4. 多级缓存中的一级缓存全网流量分发CDN
  5. eclipse设置文档注释的格式
  6. linux 英伟达 分辨率,配置nVidia显卡修改Ubuntu分辨率
  7. python微信接口发送消息_Python 微信公众号发送消息
  8. Linux(UOS) Qt不能播放音频的问题
  9. Qt 3D的未来展望
  10. 【小米校招笔试】一个数组是由有序数组经过n次循环移动后所得,请你用最快速度查找某个元素位置