2019年8月15日星期四
一. 线程互斥方式 - 互斥锁。
1. 什么是互斥锁?特点如何?
互斥锁是专门用于处理线程互斥的一个方式,它有两种状态:上锁状态/解锁状态。
特点:如果处理上锁状态,则不能再上锁,直到解锁为止才能再上锁。如果是处于解锁状态,则不能再解锁了,直到上锁了才能再解锁。
2. 关于线程互斥锁API函数接口?
0)定义互斥锁的变量(pthread_mutex_t-> 互斥锁的数据类型)
pthread_mutex_t mutex;
1)初始化互斥锁 -> pthread_mutex_init() -> man 3 pthread_mutex_init
功能: initialize a mutex -> 初始化互斥锁
使用格式:
#include <pthread.h>
-> 动态初始化
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);
mutex: 互斥锁的地址
attr: 互斥锁的属性,一般填NULL
返回值:
成功:0
失败:错误码
-> 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2)上锁 -> pthread_mutex_lock() -> man 3 pthread_mutex_lock
功能:lock a mutex
使用格式:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex:必须是已经初始化过的互斥锁的地址
返回值:
成功:0
失败:错误码
3)解锁 -> pthread_mutex_unlock() -> man 3 pthread_mutex_unlock
功能:unlock a mutex
使用格式:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex:必须是已经初始化过的互斥锁的地址
返回值:
成功:0
失败:错误码
4)销毁互斥锁 -> pthread_mutex_destroy() -> man 3 pthread_mutex_destroy
功能:
使用格式:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:必须是已经初始化过的互斥锁的地址
返回值:
成功:0
失败:错误码
练习1:使用互斥锁完成作业2。
#include "head.h"
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
//Jack发
void *routine1(void *arg)
{
char *p = (char *)arg;
while(1)
{
//在访问共享内存前必须先上锁
pthread_mutex_lock(&m);
fgets(p,1024,stdin); //访问共享内存
//访问完共享内存后必须要解锁
pthread_mutex_unlock(&m);
if(strncmp(p,"quit",4) == 0)
{
break;
}
usleep(100000);
}
pthread_exit(NULL);
}
//Rose收
void *routine2(void *arg)
{
usleep(100000);
char *p = (char *)arg;
while(1)
{
//在访问共享内存前必须先上锁,由于睡觉了,后上锁
pthread_mutex_lock(&m);
printf("from Jack:%s",p);
pthread_mutex_unlock(&m);
if(strncmp(p,"quit",4) == 0)
{
break;
}
usleep(200000);
}
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
//1. 申请key与ID号
key_t key = ftok(".",10);
int shmid = shmget(key,1024,IPC_CREAT|0666);
//2. 映射内存空间
char *p = shmat(shmid,NULL,0);
//3. 产生Jack线程与Rose线程
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,routine1,(void *)p);
pthread_create(&tid2,NULL,routine2,(void *)p);
//4. 接合线程
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//5. 撤销映射,删除共享内存ID
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
结论:只要工程涉及到共享资源(共享内存,链表,文件..),就必须在访问前上锁,访问后解锁。
二. 读写锁。
1. 互斥锁有什么缺陷?
互斥锁无论是访问资源,还是修改资源都必须要上锁,而且在上锁期间不能被别的线程再上锁。
访问资源(一起读这本书) -> 同时上读锁 -> 读锁其实是一把共享锁。
修改资源(修改书本的数据) -> 不能同时上写锁 -> 写锁其实是一把互斥锁。
即有读锁,又有写锁,那么我们就称之为读写锁。
2. 关于读写锁的API函数接口?
1)初始化读写锁 -> pthread_rwlock_init() -> man 3 pthread_rwlock_init
功能:initialize a read-write lock object
使用格式:
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);
rwlock: 读写锁的地址
attr: 属性,一般填NULL
返回值:
成功:0
失败:错误码
2)读锁上锁 -> pthread_rwlock_rdlock() -> man 3 pthread_rwlock_rdlock
功能:lock a read-write lock object for reading
使用格式:
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
rwlock:读写锁的地址
返回值:
成功:0
失败:错误码
3)写锁上锁 -> pthread_rwlock_wrlock() -> man 3 pthread_rwlock_wrlock
功能:lock a read-write lock object for writing
使用格式:
#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
rwlock:读写锁的地址
返回值:
成功:0
失败:错误码
4)读写锁解锁 -> pthread_rwlock_unlock() -> man 3 pthread_rwlock_unlock
功能:unlock a read-write lock object
使用格式:
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
rwlock:读写锁的地址
返回值:
成功:0
失败:错误码
5)销毁读写锁 -> pthread_rwlock_destroy() -> man 3 pthread_rwlock_destroy
功能:destroy a read-write lock object
使用格式:
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
rwlock:读写锁的地址
返回值:
成功:0
失败:错误码
练习2:临界资源"int a",两个线程想打印a的值,两个线程想修改a的值。
验证:
1)读锁可以同时上,但是写锁不可以同时上。
2)读锁写锁能不能同时上? -> 不可以,写锁等到读锁解开之后才能上写锁。
#include "head.h"
int a = 100;//临界资源
pthread_rwlock_t rwlock; //读写锁变量
//线程1任务:打印a的值,花了3秒
void *fun1(void *arg)
{
//访问临界资源前,需要上读锁
pthread_rwlock_rdlock(&rwlock);
printf("fun1 rdlock lock!\n");
printf("fun1 a = %d\n",a);
sleep(3);
//访问完临界资源,需要解锁
pthread_rwlock_unlock(&rwlock);
printf("fun1 rdlock unlock!\n");
pthread_exit(NULL);
}
//线程2任务:打印a的值,花了5秒
void *fun2(void *arg)
{
//访问临界资源前,需要上读锁
pthread_rwlock_rdlock(&rwlock);
printf("fun2 rdlock lock!\n");
printf("fun2 a = %d\n",a);
sleep(5);
//访问完临界资源,需要解锁
pthread_rwlock_unlock(&rwlock);
printf("fun2 rdlock unlock!\n");
pthread_exit(NULL);
}
//线程3任务:修改a的值,花了4S a = 50
void *fun3(void *arg)
{
pthread_rwlock_wrlock(&rwlock);
printf("fun3 wrlock lock!\n");
a = 50;
sleep(4);
pthread_rwlock_unlock(&rwlock);
printf("fun3 wrlock unlock!\n");
pthread_exit(NULL);
}
//线程4任务:修改a的值,花了6S a = 30
void *fun4(void *arg)
{
pthread_rwlock_wrlock(&rwlock);
printf("fun4 wrlock lock!\n");
a = 30;
sleep(6);
pthread_rwlock_unlock(&rwlock);
printf("fun4 wrlock unlock!\n");
pthread_exit(NULL);
}
void *routine(void *arg)
{
int i;
for(i=0;i<1000000;i++)
{
printf("%d\n",i);
sleep(1);
}
}
int main(int argc,char *argv[])
{
//0. 计算时间的流逝
pthread_t tid;
pthread_create(&tid,NULL,routine,NULL);
//1. 初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
//2. 产生子线程
pthread_t tid1,tid2,tid3,tid4;
pthread_create(&tid1,NULL,fun1,NULL);
pthread_create(&tid2,NULL,fun2,NULL);
pthread_create(&tid3,NULL,fun3,NULL);
pthread_create(&tid4,NULL,fun4,NULL);
//3. 接合线程
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_join(tid3,NULL);
pthread_join(tid4,NULL);
//4. 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
三. 条件变量。
1. 什么是条件变量?特点?
线程因为某个条件不成立/情况不允许情况下,进入一个变量中等待,这个可以存放这些线程的变量就叫做条件变量。
条件变量一定是与互斥锁一起使用。
2. 关于条件变量的函数接口?
0)定义条件变量 (数据类型:pthread_cond_t)
pthread_cond_t v;
1)初始化条件变量? -> pthread_cond_init() -> man 3 pthread_cond_init
功能:initialize condition variables
使用格式:
动态初始化:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);
cond: 条件变量的地址
attr: 条件变量的属性,一般填NULL。
返回值:
成功:0
失败:错误码
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2)如何进入条件变量中等待? -> pthread_cond_wait() -> man 3 pthread_cond_wait
如果满足进入条件变量的条件,则会进去等待,并且会自动解锁。
功能:wait on a condition
使用格式:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
cond:条件变量的地址
mutex:互斥锁的地址
返回值:
成功:0
失败:错误码
3)如何唤醒条件变量中等待的线程?
广播: 唤醒所有在条件变量中等待的线程。 -> pthread_cond_broadcast() -> man 3 pthread_cond_broadcast
单播: 随机唤醒条件变量中任意一个线程。 -> pthread_cond_signal() -> man 3 pthread_cond_signal
功能:broadcast or signal a condition
使用格式:
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
cond:条件变量的地址
返回值:
成功:0
失败:错误码
注意: 从条件变量中出来的线程,会自动上锁。
4)销毁条件变量 -> pthread_cond_destroy() -> man 3 pthread_cond_destroy
功能:destroy condition variables
使用格式:
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
cond:条件变量的地址
返回值:
成功:0
失败:错误码
#include "head.h"
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;//初始化互斥锁
pthread_cond_t v = PTHREAD_COND_INITIALIZER;//初始化条件变量
int sum = 400;
//任务: 就是去银行卡扣200块
void *routine(void *arg)
{
//1. 访问临界资源(银行卡)前,都需要上锁
pthread_mutex_lock(&m);
//2. 询问条件是否满足?
while(sum < 200) //当拿不到钱时
{
//就进入条件变量中等待
pthread_cond_wait(&v,&m); //自动解锁。
}
/* 从循环出来,一定是被唤醒并且余额>=200 */
printf("before money:%d\n",sum);
//拿钱
sum -= 200;
printf("after money:%d\n",sum);
//拿到钱后,要主动解锁。
pthread_mutex_unlock(&m);
//走人
pthread_exit(NULL);
}
void *routine1(void *arg)
{
int i;
for(i=0;i<1000000;i++)
{
printf("%d\n",i);
sleep(1);
}
}
int main(int argc,char *argv[])
{
//0. 计算时间的流逝
pthread_t tid_test;
pthread_create(&tid_test,NULL,routine1,NULL);
int i;
pthread_t tid[5];
//1. 产生5个子线程
for(i=0;i<5;i++)
{
pthread_create(&tid[i],NULL,routine,NULL);
}
sleep(5); //2个能拿到钱,3个在条件变量中睡觉。
/* 主线程在访问临界资源前,也必须上锁 */
pthread_mutex_lock(&m);
sum += 400;
printf("main thread + 400!\n");
//拿完钱后,需要解锁
pthread_mutex_unlock(&m);
sleep(2);
//唤醒所有的线程起来拿钱
pthread_cond_broadcast(&v); //2个拿到钱,还有1个因为拿不到钱再回去睡觉。
printf("broarcast!\n");
sleep(3);
pthread_mutex_lock(&m);
sum += 200;
printf("main thread + 200!\n");
//拿完钱后,需要解锁
pthread_mutex_unlock(&m);
pthread_cond_signal(&v);
printf("signal!\n");
for(i=0;i<5;i++)
{
pthread_join(tid[i],NULL);
}
pthread_mutex_destroy(&m);
pthread_cond_destroy(&v);
return 0;
}