08_线程的同步与互斥
间,只能一个任务(进程或线程)执行,有顺序的运行。同步 是特殊的 互斥。知识点2【互斥锁】用于线程的互斥。互斥
锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁(lock )和解锁( unlock
)互斥锁的操作流程如下:1)在访问共享资源临界区域前,对互斥锁进行加锁。2)在访问完成后释放互斥锁上的锁。
(解锁)3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。互斥锁的数据类型是
: pthread_mutex_t1、Pthread_mutex_init 函数1 #include <pth
read。h>2 int pthread_mutex_init(pthread_mutex_t *mutex,
3 const pthread_mutexattr_t *attr);功能:初始化一个互斥锁。参数:mutex
:互斥锁地址。类型是 pthread_mutex_t 。attr:设置互斥量的属性,通常可采用默认属性,即可将
attr 设为 NULL。可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,
比如:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;这种
方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始
化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。返回值:成功:0,成功
申请的锁默认是打开的。失败:非 0 错误码2、销毁互斥锁1 #include <pthread。h>2 int
pthread_mutex_destroy(pthread_mutex_t *mutex);功能:销毁指定的
一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。参数:mutex:互斥锁地址。返回值:成功
:0失败:非 0 错误码3、申请上锁1 #include <pthread。h>2 int pthread_m
utex_lock(pthread_mutex_t *mutex);功能:对互斥锁上锁,若互斥锁已经上锁,则调
用者阻塞,直到互斥锁解锁后再上锁。参数:mutex:互斥锁地址。返回值:成功:0失败:非 0 错误码1 int
pthread_mutex_trylock(pthread_mutex_t *mutex);调用该函数时,若
互斥锁未加锁,则上锁,返回 0;若互斥锁已加锁,则函数直接返回失败,即 EBUSY。4、解锁1 #includ
e <pthread。h>2 int pthread_mutex_unlock(pthread_mutex_t
*mutex);功能:对指定的互斥锁解锁。参数:mutex:互斥锁地址。返回值:成功:0失败:非0错误码案例
1:没有互斥锁 多任务的运行情况1 #include <stdio。h>2 #include <pthread
。h>3 #include <unistd。h>4 void *deal_fun01(void *arg)5
{6 char *str = (char *)arg;7 int i = 0;8 while (str[i]
!= '\0')9 {10 printf("%c", str[i++]);11 fflush(stdout);
//强制刷新12 sleep(1);13 }1415 return NULL;16 }1718 void *
deal_fun02(void *arg)19 {20 char *str = (char *)arg;21
int i = 0;22 while (str[i] != '\0')23 {24 printf("%c",
str[i++]);25 fflush(stdout); //强制刷新26 sleep(1);27 }2829
return NULL;30 }31 int main(int argc, char const *argv
[])32 {33 //创建两个线程3435 pthread_t tid1, tid2;36 pthread_
create(&tid1, NULL, deal_fun01, "hello");37 pthread_cre
ate(&tid2, NULL, deal_fun02, "world");3839 pthread_join
(tid1, NULL);40 pthread_join(tid2, NULL);4142 return 0;
43 }案例2:有互斥锁 多任务的运行情况1 #include <stdio。h>2 #include <pt
hread。h>3 #include <unistd。h>4 //定义一把锁5 pthread_mutex_t
mutex;67 void *deal_fun01(void *arg)8 {9 char *str = (
char *)arg;10 int i = 0;1112 //上锁13 pthread_mutex_lock(
&mutex);1415 while (str[i] != '\0')16 {17 printf("%c",
str[i++]);18 fflush(stdout); //强制刷新19 sleep(1);20 }2122
//解锁23 pthread_mutex_unlock(&mutex);2425 return NULL;2
6 }2728 void *deal_fun02(void *arg)29 {30 char *str = (
char *)arg;31 int i = 0;3233 //上锁34 pthread_mutex_lock(
&mutex);3536 while (str[i] != '\0')37 {38 printf("%c",
str[i++]);39 fflush(stdout); //强制刷新40 sleep(1);41 }4243
//解锁44 pthread_mutex_unlock(&mutex);4546 return NULL;4
7 }48 int main(int argc, char const *argv[])49 {50 //初始
化一把锁51 pthread_mutex_init(&mutex, NULL);5253 //创建两个线程54
pthread_t tid1, tid2;55 pthread_create(&tid1, NULL, de
al_fun01, "hello");56 pthread_create(&tid2, NULL, deal_
fun02, "world");5758 pthread_join(tid1, NULL);59 pthrea
d_join(tid2, NULL);6061 //销毁锁62 pthread_mutex_destroy(&
mutex);6364 return 0;65 }66 #include <stdio。h>67 #inclu
de <pthread。h>68 #include <unistd。h>69 //定义一把锁70 pthrea
d_mutex_t mutex;7172 void *deal_fun01(void *arg)73 {74
char *str = (char *)arg;75 int i = 0;7677 //上锁78 pthrea
d_mutex_lock(&mutex);7980 while (str[i] != '\0')81 {82
printf("%c", str[i++]);83 fflush(stdout); //强制刷新84 slee
p(1);85 }8687 //解锁88 pthread_mutex_unlock(&mutex);8990
return NULL;91 }9293 void *deal_fun02(void *arg)94 {95
char *str = (char *)arg;96 int i = 0;9798 //上锁99 pthrea
d_mutex_lock(&mutex);100101 while (str[i] != '\0')102 {
103 printf("%c", str[i++]);104 fflush(stdout); //强制刷新10
5 sleep(1);106 }107108 //解锁109 pthread_mutex_unlock(&mu
tex);110111 return NULL;112 }113 int main(int argc, cha
r const *argv[])114 {115 //初始化一把锁116 pthread_mutex_init
(&mutex, NULL);117118 //创建两个线程119 pthread_t tid1, tid2;
120 pthread_create(&tid1, NULL, deal_fun01, "hello");12
1 pthread_create(&tid2, NULL, deal_fun02, "world");1221
23 pthread_join(tid1, NULL);124 pthread_join(tid2, NULL
);125126 return 0;127 }总结:如果是互斥 不管有多少个任务,只需要一把锁,所有的任务上锁
访问资源 解锁。知识点3【死锁】知识点4【读写锁】POSIX 定义的读写锁的数据类型是: pthread_r
wlock_t1、初始化读写锁1 #include <pthread。h>2 int pthread_rwlo
ck_init(pthread_rwlock_t *rwlock,3 const pthread_rwlock
attr_t *attr);功能:用来初始化 rwlock 所指向的读写锁。参数:rwlock:指向要初始化的
读写锁指针。attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用
指定的 attr 初始化读写锁。可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读
写锁,比如:pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITI
ALIZER;这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init
() 来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。返
回值:成功:0,读写锁的状态将成为已初始化和已解锁。失败:非 0 错误码。2、释放读写锁1 #include
<pthread。h>2 int pthread_rwlock_destroy(pthread_rwlock_
t *rwlock);功能:用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由pthread_rw
lock_init() 自动申请的资源) 。参数:rwlock:读写锁指针。返回值:成功:0失败:非 0 错误
码3、申请读锁1 #include <pthread。h>2 int pthread_rwlock_rdloc
k(pthread_rwlock_t *rwlock);功能:以阻塞方式在读写锁上获取读锁(读锁定)。如果没有
写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。如果调用线程未获取读锁,则它将阻塞直到它获取了该
锁。一个线程可以在一个读写锁上多次执行读锁定。线程可以成功调用 pthread_rwlock_rdlock()
函数 n 次,但是之后该线程必须调用pthread_rwlock_unlock() 函数 n 次才能解除锁定
。参数:rwlock:读写锁指针。返回值:成功:0失败:非 0 错误码1 int pthread_rwlock
_tryrdlock(pthread_rwlock_t *rwlock);用于尝试以非阻塞的方式来在读写锁上获
取读锁。如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回4、申请写锁1 #include <p
thread。h>2 int pthread_rwlock_wrlock(pthread_rwlock_t *
rwlock);功能:在读写锁上获取写锁(写锁定)。如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程
会获取写锁。如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。参数:rwlock:读写锁指针。返回值:成功:
0失败:非 0 错误码1 int pthread_rwlock_trywrlock(pthread_rwloc
k_t *rwlock);用于尝试以非阻塞的方式来在读写锁上获取写锁。如果有任何的读者或写者持有该锁,则立即失
败返回5、释放读写锁1 #include <pthread。h>2 int pthread_rwlock_un
lock(pthread_rwlock_t *rwlock);功能:无论是读锁或写锁,都可以通过此函数解锁。参
数:rwlock:读写锁指针。返回值:成功:0失败:非 0 错误码案例1:两个任务读 一个任务写1 #incl
ude <stdio。h>2 #include <pthread。h>3 #include <unistd。h
>4 //定义一把锁5 pthread_rwlock_t rwlock;67 void *read_data0
1(void *arg)8 {9 int *p = (int *)arg;10 while (1)11 {12
//申请上读锁13 pthread_rwlock_rdlock(&rwlock);14 printf("任务
A:num=%d\n", *p);15 //解读写锁16 pthread_rwlock_unlock(&rwl
ock);17 sleep(1);18 }1920 return NULL;21 }22 void *read
_data02(void *arg)23 {24 int *p = (int *)arg;25 while (
1)26 {27 //申请上读锁28 pthread_rwlock_rdlock(&rwlock);2930
printf("任务B:num=%d\n", *p);31 //解读写锁32 pthread_rwlock_u
nlock(&rwlock);33 sleep(1);34 }35 return NULL;36 }37 vo
id *write_data(void *arg)38 {39 int *p = (int *)arg;40
while (1)41 {42 //申请写锁43 pthread_rwlock_wrlock(&rwlock)
;44 (*p)++;45 //解读写锁46 pthread_rwlock_unlock(&rwlock);4
7 printf("任务C:写入num=%d\n", *p);48 sleep(2);49 }50 retur
n NULL;51 }5253 int main(int argc, char const *argv[])5
4 {55 //定义一个公共资源56 int num = 0;5758 //初始化一把锁59 pthread_
rwlock_init(&rwlock, NULL);6061 //创建两个线程62 pthread_t ti
d1, tid2, tid3;63 pthread_create(&tid1, NULL, read_data
01, (void *)&num); //读64 pthread_create(&tid2, NULL, re
ad_data02, (void *)&num); //读65 pthread_create(&tid3, N
ULL, write_data, (void *)&num); //写6667 pthread_join(ti
d1, NULL);68 pthread_join(tid2, NULL);69 pthread_join(t
id3, NULL);7071 //销毁锁72 pthread_rwlock_destroy(&rwlock)
;73 return 0;74 }知识点5【条件变量】(重要)条件变量是用来等待而不是用来上锁的,条件变量本身
不是锁。条件变量和互斥锁同时使用。条件变量的两个动作: 条件不满, 阻塞线程 当条件满足, 通知阻塞的线程开始
工作。条件变量的类型: pthread_cond_t。1、条件变量初始化1 #include <pthread
。h>2 int pthread_cond_init(pthread_cond_t *cond,3 const
pthread_condattr_t *attr);功能:初始化一个条件变量参数:cond:指向要初始化的条
件变量指针。attr:条件变量属性,通常为默认值,传NULL即可也可以使用静态初始化的方法,初始化条件变量:p
thread_cond_t cond = PTHREAD_COND_INITIALIZER;返回值:成功:0失
败:非0错误号2、释放条件变量1 #include <pthread。h>2 int pthread_cond
_destroy(pthread_cond_t *cond);功能:销毁一个条件变量参数:cond:指向要初始
化的条件变量指针返回值:成功:0失败:非0错误号3、等待条件1 #include <pthread。h>2 i
nt pthread_cond_wait(pthread_cond_t *cond,3 pthread_mut
ex_t *mutex);功能:阻塞等待一个条件变量先解锁、等待条件满足、重新上锁(3步为原子操作)解阻塞参数
:cond:指向要初始化的条件变量指针mutex:互斥锁返回值:成功:0失败:非0错误号1 int pthre
ad_cond_timedwait(pthread_cond_t *cond,2 pthread_mutex_
t *mutex,3 const struct *abstime);功能:限时等待一个条件变量参数:cond:
指向要初始化的条件变量指针mutex:互斥锁abstime:绝对时间返回值:成功:0失败:非0错误号4、唤醒等
待在条件变量上的线程1 #include <pthread。h>2 int pthread_cond_sign
al(pthread_cond_t *cond);功能:唤醒至少一个阻塞在条件变量上的线程参数cond:指向要
初始化的条件变量指返回值成功:0失败:非0错误号1 int pthread_cond_broadcast(pt
hread_cond_t *cond);功能:唤醒全部阻塞在条件变量上的线程参数:cond:指向要初始化的条件
变量指针返回值:成功:0失败:非0错误号5、案例:生产者和消费者1 #include <stdio。h>2 #
include <pthread。h>3 #include <unistd。h>4 #include <tim
e。h>5 #include <stdlib。h>6 //定义互斥锁7 pthread_mutex_t mut
ex;8 //定义条件变量9 pthread_cond_t cond;10 //定义一个仓库 默认有3个产品1
1 int num = 3;12 void *consumption_function(void *arg)
//消费13 {14 while (1)15 {16 //申请上锁17 pthread_mutex_lock(
&mutex);1819 //判断仓库是否为空 如果为空 等待条件变量满足20 if (num == 0) /
/仓库为空21 {22 printf("%s发现仓库为空 等待生产\n", (char *)arg);23 p
thread_cond_wait(&cond, &mutex);24 }2526 //进入仓库购买产品27 i
f (num > 0)28 {29 ‐‐num;30 printf("%s购买了一个产品 仓库剩余%d个\n"
, (char *)arg, num);31 //使用产品32 printf("%s正在使用产品\n", (c
har *)arg);33 }3435 //解锁36 pthread_mutex_unlock(&mutex)
;3738 sleep(rand() % 5);39 }40 return NULL;41 }4243 voi
d *production_function(void *arg) //生产44 {45 while (1)4
6 {47 //生产一个产品48 sleep(rand() % 5);4950 //上锁 进入仓库51 pth
read_mutex_lock(&mutex);5253 //将产品放入仓库54 num++;55 print
f("%s 放入一个产品, 仓库剩余%d个\n", (char *)arg, num);5657 //通知 条
件变量阻塞的线程58 pthread_cond_broadcast(&cond);5960 //解锁61 pt
hread_mutex_unlock(&mutex);62 }63 return NULL;64 }6566
int main(int argc, char const *argv[])67 {68 //设置随机数种子6
9 srand(time(NULL));70 //初始化锁71 pthread_mutex_init(&mut
ex, NULL);72 //初始化条件变量73 pthread_cond_init(&cond, NULL)
;7475 pthread_t tid1, tid2, tid3;76 pthread_create(&tid
1, NULL, consumption_function, "消费者A");77 pthread_creat
e(&tid2, NULL, consumption_function, "消费者B");78 pthread
_create(&tid3, NULL, production_function, "生产者A");7980
//等线程结束81 pthread_join(tid1, NULL);82 pthread_join(tid2
, NULL);83 pthread_join(tid3, NULL);8485 //销毁锁86 pthrea
d_mutex_destroy(&mutex);87 //销毁条件变量88 pthread_cond_dest
roy(&cond);89 return 0;90 }知识点6【信号量】1、信号量信号量广泛用于进程或线程间的
同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当信号量值大于 0 时,则可以访
问,否则将阻塞。PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。号量数据类
型为:sem_t信号量用于互斥:不管多少个任务互斥 只需要一个信号量。先P 任务 在 V信号量用于同步:有多少
个任务 就需要多少个信号量。最先执行的任务对应的信号量为1,其他信号量全部为0。每任务先P自己 任务 V下一个
任务的信号量2、信号量的API1、初始化信号量1 #include <semaphore。h>2 int se
m_init(sem_t *sem, int pshared, unsigned int value)功能:创
建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。参数:sem:信号量的地址pshared:等
于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。value:信号量的初始值返回值:成功:0失
败: - 12、信号量减一 P操作1 int sem_wait(sem_t *sem);功能: 将信号量减一,
如果信号量的值为0 则阻塞,大于0可以减一参数:信号量的地址返回值:成功返回0 失败返回-1尝试对信号量减一1
int sem_trywait(sem_t *sem);功能: 尝试将信号量减一,如果信号量的值为0 不阻塞
,立即返回 ,大于0可以减一参数:信号量的地址返回值:成功返回0 失败返回-13、信号量加一 V操作1 int
sem_post(sem_t *sem);功能:将信号量加一参数:信号量的地址返回值:成功返回0 失败返回-
14、销毁信号量1 int sem_destroy(sem_t *sem);功能: 销毁信号量参数: 信号量的
地址返回值:成功返回0 失败返回-1知识点7【信号量用于线程的互斥】1 #include <stdio。h>2
#include <pthread。h>3 #include <semaphore。h>4 #include
<unistd。h>5 //定义一个信号量(用于互斥)6 sem_t sem;78 void my_prin
tf(char *str)9 {10 int i = 0;11 while (str[i] != '\0')1
2 {13 printf("%c", str[i++]);14 fflush(stdout);15 sleep
(1);16 }17 return;18 }19 void *task_fun01(void *arg)20
{21 //P 操作22 sem_wait(&sem);2324 my_printf((char *)arg)
;2526 //V 操作27 sem_post(&sem);2829 return NULL;30 }31 v
oid *task_fun02(void *arg)32 {33 //P 操作34 sem_wait(&sem
);3536 my_printf((char *)arg);3738 //V 操作39 sem_post(&s
em);4041 return NULL;42 }43 void *task_fun03(void *arg)
44 {45 //P 操作46 sem_wait(&sem);4748 my_printf((char *)a
rg);4950 //V 操作51 sem_post(&sem);5253 return NULL;54 }5
5 int main(int argc, char const *argv[])56 {57 //信号量初始化
为1 第二个参数0表示用于线程58 sem_init(&sem, 0, 1);5960 pthread_t t
id1, tid2, tid3;6162 pthread_create(&tid1, NULL, task_f
un01, "hello");63 pthread_create(&tid2, NULL, task_fun0
2, "world");64 pthread_create(&tid3, NULL, task_fun03,
"beijing");6566 pthread_join(tid1, NULL);67 pthread_joi
n(tid2, NULL);68 pthread_join(tid3, NULL);6970 //销毁信号量7
1 sem_destroy(&sem);72 return 0;73 }知识点8【信号量用于线程的同步】1 #
include <stdio。h>2 #include <pthread。h>3 #include <sema
phore。h>4 #include <unistd。h>5 //定义三个信号量(用于同步)6 sem_t s
em1;7 sem_t sem2;8 sem_t sem3;910 void my_printf(char *
str)11 {12 int i = 0;13 while (str[i] != '\0')14 {15 pr
intf("%c", str[i++]);16 fflush(stdout);17 sleep(1);18 }
19 return;20 }21 void *task_fun01(void *arg)22 {23 //P
操作24 sem_wait(&sem1);2526 my_printf((char *)arg);2728 /
/V 操作29 sem_post(&sem2);3031 return NULL;32 }33 void *t
ask_fun02(void *arg)34 {35 //P 操作36 sem_wait(&sem2);373
8 my_printf((char *)arg);3940 //V 操作41 sem_post(&sem3);
4243 return NULL;44 }45 void *task_fun03(void *arg)46 {
47 //P 操作48 sem_wait(&sem3);4950 my_printf((char *)arg)
;5152 //V 操作53 sem_post(&sem1);5455 return NULL;56 }57
int main(int argc, char const *argv[])58 {59 //信号量初始化为1
第二个参数0表示用于线程60 sem_init(&sem1, 0, 1);61 sem_init(&sem2
, 0, 0);62 sem_init(&sem3, 0, 0);6364 pthread_t tid1, t
id2, tid3;6566 pthread_create(&tid1, NULL, task_fun01,
"hello");67 pthread_create(&tid2, NULL, task_fun02, "wo
rld");68 pthread_create(&tid3, NULL, task_fun03, "beiji
ng");6970 pthread_join(tid1, NULL);71 pthread_join(tid2
, NULL);72 pthread_join(tid3, NULL);7374 //销毁信号量75 sem_
destroy(&sem1);76 sem_destroy(&sem2);77 sem_destroy(&se
m3);7879 return 0;80 }知识点9【无名信号量 用于 血缘关系的进程间互斥】使用mmap完成
无名信号量的定义;1 #include <stdio。h>2 #include <semaphore。h>3
#include <unistd。h>4 #include <sys/mman。h>5 //定义三个信号量(用
于同步)6 sem_t sem1;7 sem_t sem2;8 sem_t sem3;910 void my_
printf(char *str)11 {12 int i = 0;13 while (str[i] != '
\0')14 {15 printf("%c", str[i++]);16 fflush(stdout);17
sleep(1);18 }19 return;20 }2122 int main(int argc, char
const *argv[])23 {24 //定义一个无名信号量25 //MAP_ANONYMOUS匿名映射
‐1不需要文件描述符26 sem_t *sem = mmap(NULL, sizeof(sem_t), PR
OT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, ‐1, 0
);2728 //无名信号量的初始化 第一个1表示进程 第二个1表示初始化值129 sem_init(sem,
1, 1);3031 pid_t pid = fork();32 if (pid == 0) //子进程33
{34 //P操作35 sem_wait(sem);3637 my_printf("hello");3839
//V操作40 sem_post(sem);41 }42 else if (pid > 0) //父进程43
{44 //P操作45 sem_wait(sem);4647 my_printf("world");4849
//V操作50 sem_post(sem);51 }5253 //销毁信号量54 sem_destroy(s
em);55 return 0;56 }知识点10【无名信号量 用于 血缘关系的进程间同步】1 #includ
e <stdio。h>2 #include <semaphore。h>3 #include <unistd。h
>4 #include <sys/mman。h>5 //定义三个信号量(用于同步)6 sem_t sem1;7
sem_t sem2;8 sem_t sem3;910 void my_printf(char *str)1
1 {12 int i = 0;13 while (str[i] != '\0')14 {15 printf(
"%c", str[i++]);16 fflush(stdout);17 sleep(1);18 }19 re
turn;20 }2122 int main(int argc, char const *argv[])23
{24 //定义一个无名信号量25 //MAP_ANONYMOUS匿名映射 ‐1不需要文件描述符26 sem_
t *sem1 = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WR
ITE, MAP_SHARED | MAP_ANON