目录
概念
线程同步,指一个线程发出某一功能调用时,在没有得到结果前,该调用不返回,同时其它线程为保证数据一致性,不能调用该功能。
协同步调,对公共区域数据按序访问。防止数据混乱,产生与时间有关的错误。
数据混乱的原因:
1. 资源共享(独享资源则不会)
2. 调度随机(意味着数据访问会出现竞争)
3. 线程间缺乏必要同步机制
互斥锁
Linux提供mutex(互斥锁)
锁本身不具备强制性,所有线程应该在访问公共数据前拿锁
主要应用函数:
pthread_mutex_init 函数 初始化 锁初值1
pthread_mutex_destory 函数 销毁锁
pthread_mutex_lock 函数 加锁 锁-- 阻塞线程
pthread_mutex_unlock 函数 解锁p 锁++ 唤醒阻塞在锁上的线程
pthread_mutex_t lock; 创建锁
以上函数成功返回0,失败返回errno,pthread_mutex_t类型,本质是结构体,可以当成整数看待(1/0)
锁的注意事项:
锁的粒度越小越好,访问共享数据前加锁,访问结束立即解锁
pthread_trylock函数
尝试加锁,成功则--,失败返回设置错误号
死锁
一种使用锁不恰当导致的现象:
1. 对一个锁反复lock
2. 线程1拥有A锁,请求获得B锁,线程2拥有B锁,请求获得A锁
读写锁
并行性相比互斥锁更高
特性:锁只有一把,以读方式给数据加锁为读锁,以写方式给数据加锁为写锁,读共享,写独占,写锁优先级高(排队阻塞时)
相关函数:
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_rdlock(&rwlock); / pthread_rwlock_tryrdlock(&rwlock);
pthread_rwlock_wrlock(&rwlock); / pthread_rwlock_trywrlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);
以上函数都是成功返回0,失败返回错误号。
pthread_rwlock_t 类型 用于定义一个读写锁变量
pthread_rwlock_t rwlock;
示例代码,一个全局资源,三个写进程,五个读进程 :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
int counter;
pthread_rwlock_t rwlock;
void *th_write(void *arg){
int t;
int i = (int)arg;
while(1){
t = counter;
usleep(1000);
pthread_rwlock_wrlock(&rwlock);
printf("====write %d: %lu: counter = %d ++counter=%d\n", i, pthread_self(), t, ++counter);
pthread_rwlock_unlock(&rwlock);
usleep(9000);
}
return NULL;
}
void *th_read(void *arg){
int i = (int)arg;
while(1){
pthread_rwlock_rdlock(&rwlock);
printf("----read %d: %lu: %d\n", i, pthread_self(),counter);
pthread_rwlock_unlock(&rwlock);
usleep(2000);
}
return NULL;
}
int main(int argc, char *argv[]){
int i;
pthread_t tid[8];
pthread_rwlock_init(&rwlock, NULL);
for(i=0;i<3;i++)
pthread_create(&tid[i], NULL, th_write, (void*)i);
for(i=0;i<5;i++)
pthread_create(&tid[i+3], NULL, th_read, (void*)i);
for(i=0;i<8;i++)
pthread_join(tid[i], NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
结果如下:
条件变量
本身不是锁,通常结合互斥锁来使用。
主要函数:
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上函数返回值:成功0失败返回错误号
初始化条件变量:
1. pthread_cond_init(&cond, NULL); 动态初始化。
2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 静态初始化。
pthread_cond_wait函数
原型: int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
作用:
1. 阻塞等待条件变量满足
2. 解锁已经加锁成功的信号量 (相当于 pthread_mutex_unlock(&mutex)),12两步为一个原子操作
3. 当条件满足,函数返回时,解除阻塞并重新申请获取互斥锁。重新加锁信号量 (相当于, pthread_mutex_lock(&mutex);)
生产者消费者模型
示例代码,使用条件变量模拟生产者消费者问题:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
struct msg{
struct msg *next;
int num;
};
struct msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void err_thread(int ret, char *str){
if(ret!=0){
fprintf(stderr, "%s:%s\n", str, strerror(ret));
pthread_exit(NULL);
}
}
void *consumer(void *arg){
struct msg *mp;
for(;;){
pthread_mutex_lock(&lock);
while(head==NULL){
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);
printf("-Consume %lu---%d\n", pthread_self(), mp->num);
free(mp);
sleep(rand()%5);
}
}
void *producer(void *p){
struct msg *mp;
for(;;){
mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1;
printf("-Produce ---%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(rand() % 5);
}
}
int main(int argc, char *argv[]){
int i, ret;
pthread_t pid, cid;
srand(time(NULL));
ret = pthread_create(&pid, NULL, producer, NULL);
if(ret!=0){
err_thread(ret, "pthread_create producer error");
}
ret = pthread_create(&cid, NULL, consumer, NULL);
if(ret!=0){
err_thread(ret, "pthread_create producer error");
}
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
结果如下, 消费者按链表顺序消费:
多个消费者的示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
struct msg{
struct msg *next;
int num;
};
struct msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void err_thread(int ret, char *str){
if(ret!=0){
fprintf(stderr, "%s:%s\n", str, strerror(ret));
pthread_exit(NULL);
}
}
void *consumer(void *arg){
struct msg *mp;
for(;;){
pthread_mutex_lock(&lock);
while(head==NULL){
pthread_cond_wait(&has_product, &lock);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&lock);
printf("-Consume id=%lu---%d\n", pthread_self(), mp->num);
free(mp);
sleep(rand()%5);
}
}
void *producer(void *p){
struct msg *mp;
for(;;){
mp = malloc(sizeof(struct msg));
mp->num = rand() % 1000 + 1;
printf("-Produce ---%d\n", mp->num);
pthread_mutex_lock(&lock);
mp->next = head;
head = mp;
pthread_mutex_unlock(&lock);
pthread_cond_signal(&has_product);
sleep(rand() % 5);
}
}
int main(int argc, char *argv[]){
int i, ret;
pthread_t pid, cid;
srand(time(NULL));
ret = pthread_create(&pid, NULL, producer, NULL);
if(ret!=0){
err_thread(ret, "pthread_create producer error");
}
for(i=0; i<5; i++){
ret = pthread_create(&cid, NULL, consumer, NULL);
if(ret!=0){
err_thread(ret, "pthread_create producer error");
}
}
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
注意,消费者阻塞在条件变量时应该使用while循环判断,这样在一个消费者消费完数据之后,另外一个做的第一件事不是拿锁而是去判断条件变量
信号量
信号量相当于初始化值为n的互斥量,既能保证同步,数据不混乱,又能提高线程并发。
函数:
sem_t sem; 定义类型
int sem_init(sem_t *sem, int pshared, unsigned int value); 初始化
参数:
sem: 信号量
pshared: 0: 用于线程间同步
1: 用于进程间同步
value:N值。(指定同时访问的线程数)
sem_destroy(); 销毁
sem_wait(); 一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。 (对比 pthread_mutex_lock)
sem_post(); 一次调用,做一次++ 操作. 当信号量的值为 N 时, 再次 ++ 就会阻塞。(对比 pthread_mutex_unlock)
定义两个信号量空格数和产品数,实现生产者消费者模型:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
sem_t blank_number, product_number;
void *producer(void *arg){
int i = 0;
while(1){
sem_wait(&blank_number);
queue[i] = rand() % 1000 + 1;
printf("---Produce---%d\n", queue[i]);
sem_post(&product_number);
i = (i+1) % NUM;
sleep(rand()%1);
}
}
void *consumer(void *p){
int i = 0;
while(1){
sem_wait(&product_number);
printf("---Consume---%d\n", queue[i]);
queue[i] = 0;
sem_post(&blank_number);
i = (i+1) % NUM;
sleep(rand()%3);
}
}
int main(int argc, char *argv[]){
pthread_t pid, cid;
sem_init(&blank_number, 0, NUM);
sem_init(&product_number, 0, 0);
pthread_create(&pid, NULL, producer, NULL);
pthread_create(&cid, NULL, consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
sem_destroy(&blank_number);
sem_destroy(&product_number);
return 0;
}
结果如下: