同步概念
线程同步
- 理解:多个线程之间有序的执行。
数据混乱原因
- 资源共享(独享资源不会)
- 调度随机(意味着数据访问出现竞争)
- 缺乏合理的线程调度机制
互斥量mutex
pthread_mutex_t
解释:可以简单认为是一把锁,拥有0和1两个值,0为加锁状态,1为解锁状态
主要应用函数
pthread_mutex_init
- 函数原型:
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr)
- 作用:初始化一个锁
- 返回值:成功返回0,失败返回错误号
pthread_mutex_destroy
- 函数原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
- 作用:销毁一个锁
- 返回值:成功返回0,失败返回错误号
pthread_mutex_lock
- 函数原型:
int pthread_mutex_lock(pthread_mutex_t *mutex)
- 作用:给一个锁上锁
- 返回值:成功返回0,失败返回错误号
pthread_mutex_unlock
- 函数原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex)
- 作用:给一个锁解锁
- 返回值:成功返回0,失败返回错误号
pthread_mutex_trylock
- 函数原型:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 作用:查看一个锁是否为上锁状态
- 返回值:未上锁返回0,否则返回错误号
死锁
两种造成死锁的方式
- 连续两次调用pthread_mutex_lock函数导致死锁,程序阻塞
- 线程A拥有A锁,请求B锁,但是线程B拥有B锁,请求A锁。
第二种死锁代码演示
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex1, mutex2;
void *t_fun(void * arg) {
while(1) {
pthread_mutex_lock(&mutex1);
printf("hello ");
sleep(2);
pthread_mutex_lock(&mutex2);
printf("world\n");
sleep(1);
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
}
}
int main(void) {
pthread_mutex_init(&mutex1, NULL);
pthread_mutex_init(&mutex2, NULL);
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_create(&tid, &attr, t_fun, NULL);
sleep(1);
while(1) {
pthread_mutex_lock(&mutex2);
printf("World\n");
sleep(1);
pthread_mutex_lock(&mutex1);
printf("Hello ");
}
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&mutex1);
pthread_mutex_destroy(&mutex2);
return 0;
}
如何解决死锁
- 加锁之前检查是否解锁
- 如果trylock失败,那么释放自己拥有的锁
读写锁
读写锁特性
写独占,读共享,写锁优先级高。
写锁独占,读锁可以共享,如果读写锁同时出现,写锁的优先级高于读锁
相关函数
pthread_rwlock_init
- 具体参见manpages
pthread_rwlock_destroy
- 具体参见manpages
pthread_rwlock_rdlock
- 具体参见manpages
pthread_rwlock_wrlock
- 具体参见manpages
pthread_rwlock_unlock
- 具体参见manpages
pthread_rwlock_tryrdlock
- 具体参见manpages
pthread_rwlock_trywrlock
- 具体参见manpages
以上函数仿照mutex函数,并且成功返回0,失败返回错误号
条件变量
特性:条件变量本身不是锁!但它可以造成线程阻塞。通常和互斥锁配合,给多线程提供一个回合的场所。
条件变量
相关函数
pthread_cond_init
- 具体参见manpages
pthread_cond_destroy
- 具体参见manpages
pthread_cond_wait
- 函数原型
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- 函数作用:
- 阻塞线程并等待cond条件满足,又带你想sigsuspend函数
- 解锁mutex
- 该函数被唤醒的时候返回,解除线程的阻塞,并重新获得mutex
1.2.为原子操作,也就是说在调用该函数的时候两个操作同时完成
- 返回值:成功返回0,失败返回错误号
pthread_cond_timedwait
- 相对于_wait函数,timedwait多了最后一个参数
timespec *abstime
结构体中有两个变量,一个是秒,一个是纳秒,这个时间是相对于Liunx元年(1970/1/1 0:0:0)来定义的,所以如果需要定时需要首先获取当前时间,然后+?(来设置多少秒的定时)
time_t cur = time(NULL);
timespec t = {cur + 1, 0);//用来定时1s
pthread_cond_timedwait(&cond, &mutex, &t);
pthread_cond_signal
- 函数原型:
int pthread_cond_signal(pthread_cond_t *cond);
- 函数作用:解除通过条件cond阻塞的线程
- 返回值:成功返回0,失败返回错误号
pthread_cond_broadcast
- 函数作用:解除通过条件cond阻塞的线程
- 返回值:成功返回0,失败返回错误号
生产者消费者模型
模型简介
该模型为存在一个共享数据区,消费者去消耗共享数据区的内容,生产者去生产共享数据区的数据,生产者和消费者的线程之间要相互配合,通过条件变量实现线程同步。
代码实现模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
struct Message {
Message * nex;
int num;
};
Message *head, *tail;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
void * productor(void * arg) {
while(1) {
tail = (Message *)malloc(sizeof(Message));
tail->num = rand() % 1000 + 1;
printf("product num = %d\n", tail->num);
pthread_mutex_lock(&lock);
tail->nex = head;
head = tail;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(rand() % 5);
}
return NULL;
}
void * consumer(void * arg) {
while(1) {
pthread_mutex_lock(&lock);
while(head == NULL) {
pthread_cond_wait(&has_product, &lock);
}
Message * mp = head;
head = head->nex;
pthread_mutex_unlock(&lock);
printf("Eat num = %d\n", mp->num);
free(mp);
sleep(rand() % 5);
}
return NULL;
}
int main(void) {
pthread_t pid, cid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&pid, &attr, productor, NULL);
pthread_create(&cid, &attr, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
信号量
强化版的互斥量,互斥量我们可以看作是0或1(我们只是这么认为,但是互斥量仍然是结构体)
信号量:sem_t 类型
常用函数
sem_init函数
- 函数原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 参数解释:第二个参数为0是线程间共享,为1是进程间共享,第三个参数为信号量
- 返回值成功返回0,失败返回-1
sem_destroy函数
sem_wait函数
- 相当于lock
sem_post函数
- 相当于unlock
sem_trywait函数
- 相当于trylock
sem_timedwait函数
使用信号量实现生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define NUM 5
sem_t sem_product, sem_blank;
int a[NUM];
void * fun_consumer(void * arg) {
int i = 0;
while(1) {
sem_wait(&sem_blank);
a[i] = rand() % 1000;
printf("product ------%d\n", a[i]);
sem_post(&sem_product);
i = (i + 1) % NUM;
sleep(rand() % 3);
}
return NULL;
}
void * fun_producter(void * arg) {
int i = 0;
while(1) {
sem_wait(&sem_product);
printf("consumer ----- %d\n", a[i]);
sem_post(&sem_blank);
i = (i + 1) % NUM;
sleep(rand() % 3);
}
return NULL;
}
int main(void) {
pthread_t tid[2];
sem_init(&sem_product, 0, 0);
sem_init(&sem_blank, 0, NUM);
pthread_create(&tid[0], NULL, fun_producter, NULL);
pthread_create(&tid[1], NULL, fun_consumer, NULL);
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
sem_destroy(&sem_product);
sem_destroy(&sem_blank);
return 0;
}
进程间互斥量
我们知道互斥量在线程中可以灵活的使用,但是互斥量在进程中也可以做到共享
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
struct Message {
int num;
pthread_mutex_t mutex;
pthread_mutexattr_t mutex_attr;
};
int main(void) {
srand(time(NULL));
Message * msg;
msg = (Message * )mmap(NULL, sizeof(Message), PROT_WRITE | PROT_READ, MAP_SHARED | MAP_ANON, -1, 0);
memset(msg, 0, sizeof(*msg));
pthread_mutexattr_init(&msg->mutex_attr);
pthread_mutexattr_setpshared(&msg->mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&msg->mutex, &msg->mutex_attr);
pid_t pid;
pid = fork();
if(pid == 0) {
while(1) {
pthread_mutex_lock(&msg->mutex);
msg->num++;
printf("child pthread -------- +1 %d\n", msg->num);
pthread_mutex_unlock(&msg->mutex);
sleep(rand() % 3);
}
} else if(pid > 0) {
while(1) {
pthread_mutex_lock(&msg->mutex);
msg->num += 2;
printf("------------ parent += 2 %d\n", msg->num);
pthread_mutex_unlock(&msg->mutex);
sleep(rand() % 3);
}
}
return 0;
}
文件锁
文件锁用于进程间通讯使用,类似于线程之间的读写锁,但是使用fcntl函数实现
fcntl函数
- 函数原型:
int fcntl(int fildes, int cmd, ...);
副参cmd在文件锁中的应用
在文件锁中用需要用到三个cmd
- F_SETLCK:参数为flock结构体,加锁
- F_SETLCKW:参数为flock结构体,加锁
- F_UNLOCK:解锁
⚠️需要注意的是:文件锁中加锁和解锁需要的是同一个函数,不同的是flock结构体中的东西。
flock结构体
struct flock {
off_t l_start; /* starting offset */
off_t l_len; /* len = 0 means until end of file */
pid_t l_pid; /* lock owner */
short l_type; /* lock type: read/write, etc. */
short l_whence; /* type of l_start */
};
l_type:三种类型,分别为 F_WRLCK(写锁),F_RDLCK(读锁),F_UNLCK(解锁)
l_whence:其实位置
l_start:偏移量
l_len:加锁的长度,如果为0操作的是整个文件
l_pid:拥有这个flock的进程id
⚠️注意:文件锁只能用在进程中,并不能用在线程中,因为各个线程共享文件描述符,同样共享了文件结构体,fcntl又是通过文件描述符操作的文件结构体,所以在线程中不能使用。
使用fcntl加锁解锁文件演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
void sys_err(const char * msg){
perror(msg);
exit(1);
}
int main(int argc, char ** argv) {
if(argc != 2) {
puts("./file_lock filename");
exit(1);
}
if(access(argv[1], F_OK) < 0) {
sys_err(const_cast<char *>("file not exist"));
}
int fd;
if((fd = open(argv[1], O_RDWR)) < 0) {
sys_err(const_cast<char *>("open failed"));
}
struct flock f_lock;
memset(&f_lock, 0, sizeof(f_lock));
//f_lock.l_type = F_RDLCK; /* 可选读锁 */
f_lock.l_type = F_WRLCK; /* 可选写锁 */
f_lock.l_whence = SEEK_SET;
f_lock.l_start = 0;
f_lock.l_len = 0;
fcntl(fd, F_SETLKW, &f_lock);
printf("GET LOCK!!!\n");
sleep(10);
f_lock.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &f_lock);
printf("LOSE LOCK!\n");
return 0;
}