文章目录
线程同步
同步
线程同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次访问
同步的方式
多个线程访问共享资源就需要线程同步,四种方法:互斥锁,读写锁,条件变量,信号量
//加锁
pthread_mutex_lock(&mutex);
//共享数据域
int cur=rand()%20;
number+=cur;//全局变量
if(number%2)
{
printf("%d",number);
}
//解锁
pthread_mutex_unlock(&mutex);
互斥锁
创建的锁对象中保存了当前这把锁的状态信息:锁定还是打开,如果是锁定状态还记录了给这把锁加锁的线程信息(线程 ID)。一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程再对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被解除阻塞。一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。
创建锁
pthread_mutex_t mutex;
互斥锁函数
/ 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
// 释放互斥锁资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量的地址
attr: 互斥锁的属性,一般使用默认属性即可,这个参数指定为 NULL
加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);//给代码段加锁
线程会判断当前锁的状态:
如果是上锁状态:线程会阻塞在此处
如果为未锁:线程加锁并记录锁状态,向下执行
尝试加锁
// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
线程会判断当前锁的状态:
如果是上锁状态:线程不会被阻塞,加锁失败直接返回错误号
如果为未锁:线程加锁并记录锁状态,向下执行
解锁
// 对互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
使用互斥锁进行线程同步
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#define MAX 50
// 全局变量
int number;
// 线程处理函数
void* funcA_num(void* arg)
{
for(int i=0; i<MAX; ++i)
{
int cur = number;
cur++;
usleep(10);
number = cur;
printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
}
return NULL;
}
void* funcB_num(void* arg)
{
for(int i=0; i<MAX; ++i)
{
int cur = number;
cur++;
number = cur;
printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
usleep(5);
}
return NULL;
}
int main(int argc, const char* argv[])
{
pthread_t p1, p2;
// 创建两个子线程
pthread_create(&p1, NULL, funcA_num, NULL);
pthread_create(&p2, NULL, funcB_num, NULL);
// 阻塞,资源回收
pthread_join(p1, NULL);
pthread_join(p2, NULL);
return 0;
}
/*
Thread B, id = 139740447761984, number = 1
Thread A, id = 139740456154688, number = 1
Thread B, id = 139740447761984, number = 2
Thread A, id = 139740456154688, number = 2
Thread B, id = 139740447761984, number = 3
Thread A, id = 139740456154688, number = 3
Thread B, id = 139740447761984, number = 4
Thread A, id = 139740456154688, number = 4
Thread B, id = 139740447761984, number = 5
Thread A, id = 139740456154688, number = 5
Thread A, id = 139740456154688, number = 6
Thread B, id = 139740447761984, number = 7
Thread A, id = 139740456154688, number = 7
Thread B, id = 139740447761984, number = 8
Thread B, id = 139740447761984, number = 9
Thread A, id = 139740456154688, number = 9
Thread B, id = 139740447761984, number = 10
Thread A, id = 139740456154688, number = 10
Thread B, id = 139740447761984, number = 11
Thread A, id = 139740456154688, number = 11
Thread B, id = 139740447761984, number = 12
Thread A, id = 139740456154688, number = 12
Thread B, id = 139740447761984, number = 13
Thread A, id = 139740456154688, number = 13
Thread B, id = 139740447761984, number = 14
Thread A, id = 139740456154688, number = 14
Thread B, id = 139740447761984, number = 15
Thread A, id = 139740456154688, number = 15
Thread B, id = 139740447761984, number = 16
Thread A, id = 139740456154688, number = 16
Thread B, id = 139740447761984, number = 17
Thread A, id = 139740456154688, number = 17
Thread B, id = 139740447761984, number = 18
Thread A, id = 139740456154688, number = 18
Thread B, id = 139740447761984, number = 19
Thread B, id = 139740447761984, number = 20
Thread A, id = 139740456154688, number = 19
Thread B, id = 139740447761984, number = 20
Thread A, id = 139740456154688, number = 20
Thread B, id = 139740447761984, number = 21
Thread A, id = 139740456154688, number = 21
Thread B, id = 139740447761984, number = 22
Thread A, id = 139740456154688, number = 22
Thread B, id = 139740447761984, number = 23
Thread A, id = 139740456154688, number = 23
Thread B, id = 139740447761984, number = 24
Thread A, id = 139740456154688, number = 24
Thread B, id = 139740447761984, number = 25
Thread A, id = 139740456154688, number = 25
Thread B, id = 139740447761984, number = 26
Thread A, id = 139740456154688, number = 26
Thread B, id = 139740447761984, number = 27
Thread A, id = 139740456154688, number = 27
Thread B, id = 139740447761984, number = 28
Thread A, id = 139740456154688, number = 28
Thread B, id = 139740447761984, number = 29
Thread A, id = 139740456154688, number = 29
Thread B, id = 139740447761984, number = 30
Thread B, id = 139740447761984, number = 31
Thread A, id = 139740456154688, number = 30
Thread B, id = 139740447761984, number = 31
Thread A, id = 139740456154688, number = 31
Thread B, id = 139740447761984, number = 32
Thread A, id = 139740456154688, number = 32
Thread B, id = 139740447761984, number = 33
Thread A, id = 139740456154688, number = 33
Thread B, id = 139740447761984, number = 34
Thread A, id = 139740456154688, number = 34
Thread B, id = 139740447761984, number = 35
Thread A, id = 139740456154688, number = 35
Thread B, id = 139740447761984, number = 36
Thread A, id = 139740456154688, number = 36
Thread B, id = 139740447761984, number = 37
Thread A, id = 139740456154688, number = 37
Thread B, id = 139740447761984, number = 38
Thread A, id = 139740456154688, number = 38
Thread B, id = 139740447761984, number = 39
Thread A, id = 139740456154688, number = 39
Thread B, id = 139740447761984, number = 40
Thread A, id = 139740456154688, number = 40
Thread B, id = 139740447761984, number = 41
Thread A, id = 139740456154688, number = 41
Thread B, id = 139740447761984, number = 42
Thread A, id = 139740456154688, number = 42
Thread B, id = 139740447761984, number = 43
Thread B, id = 139740447761984, number = 43
Thread A, id = 139740456154688, number = 43
Thread B, id = 139740447761984, number = 44
Thread A, id = 139740456154688, number = 44
Thread B, id = 139740447761984, number = 45
Thread A, id = 139740456154688, number = 45
Thread B, id = 139740447761984, number = 46
Thread A, id = 139740456154688, number = 46
Thread B, id = 139740447761984, number = 47
Thread A, id = 139740456154688, number = 47
Thread B, id = 139740447761984, number = 48
Thread A, id = 139740456154688, number = 48
Thread A, id = 139740456154688, number = 49
Thread A, id = 139740456154688, number = 50
Thread A, id = 139740456154688, number = 51
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#define MAX 50
// 全局变量
int number;
pthread_mutex_t mutex; // 定义一个互斥锁
// 线程处理函数
void *funcA_num(void *arg)
{
for (int i = 0; i < MAX; ++i)
{
pthread_mutex_lock(&mutex);
int cur = number;
cur++;
usleep(10);
number = cur;
printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *funcB_num(void *arg)
{
for (int i = 0; i < MAX; ++i)
{
pthread_mutex_lock(&mutex);
int cur = number;
cur++;
number = cur;
printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
pthread_mutex_unlock(&mutex);
usleep(5); // 线程休眠微秒
}
return NULL;
}
int main(int argc, const char *argv[])
{
pthread_t p1, p2;
pthread_mutex_init(&mutex, NULL);
// 创建两个子线程
pthread_create(&p1, NULL, funcA_num, NULL);
pthread_create(&p2, NULL, funcB_num, NULL);
// 阻塞,资源回收
pthread_join(p1, NULL);
pthread_join(p2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
/*
Thread A, id = 140637047354944, number = 1
Thread A, id = 140637047354944, number = 2
Thread A, id = 140637047354944, number = 3
Thread A, id = 140637047354944, number = 4
Thread A, id = 140637047354944, number = 5
Thread A, id = 140637047354944, number = 6
Thread A, id = 140637047354944, number = 7
Thread A, id = 140637047354944, number = 8
Thread A, id = 140637047354944, number = 9
Thread A, id = 140637047354944, number = 10
Thread A, id = 140637047354944, number = 11
Thread A, id = 140637047354944, number = 12
Thread A, id = 140637047354944, number = 13
Thread A, id = 140637047354944, number = 14
Thread A, id = 140637047354944, number = 15
Thread A, id = 140637047354944, number = 16
Thread A, id = 140637047354944, number = 17
Thread A, id = 140637047354944, number = 18
Thread A, id = 140637047354944, number = 19
Thread A, id = 140637047354944, number = 20
Thread A, id = 140637047354944, number = 21
Thread A, id = 140637047354944, number = 22
Thread A, id = 140637047354944, number = 23
Thread A, id = 140637047354944, number = 24
Thread A, id = 140637047354944, number = 25
Thread A, id = 140637047354944, number = 26
Thread A, id = 140637047354944, number = 27
Thread A, id = 140637047354944, number = 28
Thread A, id = 140637047354944, number = 29
Thread A, id = 140637047354944, number = 30
Thread A, id = 140637047354944, number = 31
Thread A, id = 140637047354944, number = 32
Thread A, id = 140637047354944, number = 33
Thread A, id = 140637047354944, number = 34
Thread A, id = 140637047354944, number = 35
Thread A, id = 140637047354944, number = 36
Thread A, id = 140637047354944, number = 37
Thread A, id = 140637047354944, number = 38
Thread A, id = 140637047354944, number = 39
Thread A, id = 140637047354944, number = 40
Thread A, id = 140637047354944, number = 41
Thread A, id = 140637047354944, number = 42
Thread A, id = 140637047354944, number = 43
Thread A, id = 140637047354944, number = 44
Thread A, id = 140637047354944, number = 45
Thread A, id = 140637047354944, number = 46
Thread A, id = 140637047354944, number = 47
Thread A, id = 140637047354944, number = 48
Thread A, id = 140637047354944, number = 49
Thread A, id = 140637047354944, number = 50
Thread B, id = 140637038962240, number = 51
Thread B, id = 140637038962240, number = 52
Thread B, id = 140637038962240, number = 53
Thread B, id = 140637038962240, number = 54
Thread B, id = 140637038962240, number = 55
Thread B, id = 140637038962240, number = 56
Thread B, id = 140637038962240, number = 57
Thread B, id = 140637038962240, number = 58
Thread B, id = 140637038962240, number = 59
Thread B, id = 140637038962240, number = 60
Thread B, id = 140637038962240, number = 61
Thread B, id = 140637038962240, number = 62
Thread B, id = 140637038962240, number = 63
Thread B, id = 140637038962240, number = 64
Thread B, id = 140637038962240, number = 65
Thread B, id = 140637038962240, number = 66
Thread B, id = 140637038962240, number = 67
Thread B, id = 140637038962240, number = 68
Thread B, id = 140637038962240, number = 69
Thread B, id = 140637038962240, number = 70
Thread B, id = 140637038962240, number = 71
Thread B, id = 140637038962240, number = 72
Thread B, id = 140637038962240, number = 73
Thread B, id = 140637038962240, number = 74
Thread B, id = 140637038962240, number = 75
Thread B, id = 140637038962240, number = 76
Thread B, id = 140637038962240, number = 77
Thread B, id = 140637038962240, number = 78
Thread B, id = 140637038962240, number = 79
Thread B, id = 140637038962240, number = 80
Thread B, id = 140637038962240, number = 81
Thread B, id = 140637038962240, number = 82
Thread B, id = 140637038962240, number = 83
Thread B, id = 140637038962240, number = 84
Thread B, id = 140637038962240, number = 85
Thread B, id = 140637038962240, number = 86
Thread B, id = 140637038962240, number = 87
Thread B, id = 140637038962240, number = 88
Thread B, id = 140637038962240, number = 89
Thread B, id = 140637038962240, number = 90
Thread B, id = 140637038962240, number = 91
Thread B, id = 140637038962240, number = 92
Thread B, id = 140637038962240, number = 93
Thread B, id = 140637038962240, number = 94
Thread B, id = 140637038962240, number = 95
Thread B, id = 140637038962240, number = 96
Thread B, id = 140637038962240, number = 97
Thread B, id = 140637038962240, number = 98
Thread B, id = 140637038962240, number = 99
Thread B, id = 140637038962240, number = 100
抢cpu时机片是随机的,并不一定是交替进行
*/
死锁
当多个线程访问共享资源,需要加锁,如果锁使用不当,就会造成死锁这种现象。如果线程死锁造成的后果是:所有的线程都被阻塞,并且线程的阻塞是无法解开的(因为可以解锁的线程也被阻塞了)
1.加锁之后忘记解锁
//场景1
void func()
{
for(int i=0;i<6;i++)
{
//当前线程A加锁成功,当前循环完毕没有解锁,在下一轮循环的时候自己被阻塞了
//其余的线程也被阻塞
pthread_mutex_lock(&mutex);
……
…………
//忘记解锁
}
}
//场景二
void func()
{
for(int i=0;i<6;i++)
{
//当前线程A加锁成功
//其余的线程被阻塞
pthread_mutex_lock(&mutex);
……
…………
if(xxx)
{
//符合条件,函数退出,没有解锁(解锁函数无法执行了)
return;
}
pthread_mutex_unlock(&mutex);
}
}
2.重复加锁,造成死锁
void func()
{
for(int i=0;i<6;i++)
{
//当前线程A加锁成功
//其余的线程被阻塞
pthread_mutex_lock(&mutex);
//锁被锁住了,A线程阻塞
pthread_mutex_lock(&mutex);
……
……
pthread_mutex_unlock(&mutex);
}
}
//隐藏的比较深的情况
void funcA()
{
for(int i=0;i<6;i++)
{
//当前线程A加锁成功
//其余的线程被阻塞
pthread_mutex_lock(&mutex);
……
pthread_mutex_unlock(&mutex);
}
}
void funcB()
{
for(int i=0;i<6;i++)
{
//当前线程B加锁成功
//其余的线程被阻塞
pthread_mutex_lock(&mutex);
funcA();//调用了另外一个函数,里面还有一个互斥锁
……
pthread_mutex_unlock(&mutex);
}
}
在使用多线程编程的时候,避免死锁方法:
**·**避免多次锁定,多检查
**·**对共享资源访问完毕之后,一定要解锁,或者在加锁的使用 trylock
**·**如果程序中有多把锁,可以控制对锁的访问顺序 (顺序访问共享资源,但在有些情况下是做不到的),另外也可以在对其他互斥锁做加锁操作之前,先释放当前线程拥有的互斥锁。
**·**项目程序中可以引入一些专门用于死锁检测的模块
读写锁
互斥锁并没有对读写规定,读写锁是一把锁,既可以对读进行锁定,也可以对写进行锁定
可以理解锁中记录了以下信息:
1.锁的状态:锁定/打开
2.锁定的是读/写操作,使用了 读写锁 锁定了读操作,需要先解锁才能去锁定写操作,反之亦然
3.哪个线程将这把锁锁上了
读写锁的类型pyhread_rwlock_t
pthread_rwlock_t rwlock;
读写锁的使用方式与互斥锁的使用方式是完全相同的:找共享资源,确定临界区,在临界区的开始位置加锁(读锁 / 写锁),临界区的结束位置解锁。
读写锁的特点:
1.使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
2.使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。按先后顺序依次执行
3.使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写比读的优先级高。
如果说程序中所有的线程都对共享资源做写操作,使用读写锁没有优势,和互斥锁是一样的,如果说程序中所有的线程都对共享资源有写也有读操作,并且对共享资源读的操作越多,读写锁更有优势。
读写锁函数
#include<pthread.h>
//创建读写锁
pthread_rwlock_t rwlock;
//初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
//释放读写锁占用的系统资源
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
//参数:
//rwlock:读写锁的地址,传出参数
//attr:读写锁属性,一般用默认属性,指定为NULL
//在程序中对读写锁加读锁,锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t*rwlock);//rd=read
//如果这个锁已经被写操作锁定了,读锁就会被阻塞
//如果这个锁已经被读操作锁定了,读锁就不会被阻塞
//在程序中对读写锁加写锁,锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//wr=write
//根互斥锁就差不多了
//调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数加锁失败,对应的线程不会被阻塞,可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。
//这个函数可以有效的避免死锁
//如果加读锁失败,不会阻塞当前进程,直接返回错误号
int pthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);
//调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数的线程会被阻塞。
//这个函数可以有效的避免死锁
//如果加写锁失败,不会阻塞当前进程,直接返回错误号
int pthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);
//解锁,不管锁定了读还是写都可用解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
读写锁使用
题目要求:8个线程操作同一个全局变量,3个线程不定时写同一全局资源,5个线程不定时读同一全局资源
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 全局变量
int number = 0;
// 定义读写锁
pthread_rwlock_t rwlock;
// 写的线程的处理函数
void* writeNum(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
int cur = number;
cur ++;
number = cur;
printf("++写操作完毕, number : %d, tid = %ld\n", number, pthread_self());
pthread_rwlock_unlock(&rwlock);
// 添加sleep目的是要看到多个线程交替工作
usleep(rand() % 100);
}
return NULL;
}
// 读线程的处理函数
// 多个线程可以如果处理动作相同, 可以使用相同的处理函数
// 每个线程中的栈资源是独享
void* readNum(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("--全局变量number = %d, tid = %ld\n", number, pthread_self());
pthread_rwlock_unlock(&rwlock);
usleep(rand() % 100);
}
return NULL;
}
int main()
{
// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 3个写线程, 5个读的线程
pthread_t wtid[3];
pthread_t rtid[5];
for(int i=0; i<3; ++i)
{
pthread_create(&wtid[i], NULL, writeNum, NULL);
}
for(int i=0; i<5; ++i)
{
pthread_create(&rtid[i], NULL, readNum, NULL);
}
// 释放资源
for(int i=0; i<3; ++i)
{
pthread_join(wtid[i], NULL);
}
for(int i=0; i<5; ++i)
{
pthread_join(rtid[i], NULL);
}
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
条件变量
条件变量的主要作用不是处理线程同步,而是进行线程的阻塞。
一般情况下条件变量用于处理生产者和消费者模型,并且.和互斥锁配合使用,条件变量类型对应的类型为pthread_cond_t
//定义一个条件变量
pthread_cond_t cond;
这个变量记录了被条件变量阻塞的线程的线程信息,便于在解除阻塞的时候使用。
#include<pthread.h>
pthread_cond_t cond;
//初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_cond_t *restrict attr);
//销毁释放资源
int pthread_cond_destory(pthread_cond_t *cond);
//参数
//cond:条件变量的地址
//attr:条件变量属性,一般使用默认属性,指定为NULL
//线程阻塞函数,哪个线程调用这个函数,哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_cond_t *restrict mutex);
//第一个参数是条件变量的地址,第二个变量是互斥锁的地址
// 表示的时间是从1970.1.1到某个时间点的时间, 总长度使用秒/纳秒表示,两个数相加
struct timespec {
time_t tv_sec; //秒
long tv_nsec; //纳秒s [0 .. 999999999],初始化为0
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
//前两个参数和wait函数是一样的,第三个参数表示线程阻塞的时长--用结构体描述
赋值方式
time_t mytim = time(NULL); // 1970.1.1 0:0:0 到当前的总秒数
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = time(NULL) + 100; // 线程阻塞100s
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程 全部 解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);
使用条件变量实现生产者和消费者模型,生产者有5个,往链表头部添加节点,消费者也有5个,删除链表头部的节点
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
//创建条件变量
pthread_cond_t cond;
//创建互斥锁
pthread_mutex_t mutex;
//链表节点类型
struct Node
{
int number;//节点数据值
struct Node*next;//指向当前节点类型后继节点的地址
};
//创建头结点
struct Node*head=NULL;
//生产者
//此处用的是链表没有上限
void* producer(void* arg)
{
//一直生产
while(1)
{
pthread_mutex_lock(&mutex);
//创建新节点
struct Node*newNode=(struct Node*)malloc(sizeof(struct Node));
//初始化节点
newNode->number=rand()%1000;//获取随机数,1000以内
//节点的连接,添加到链表的头部,新节点就是新的头结点
newNode->next=head;
//head指针前移
head=newNode;
printf("生产者,id:%ld ,number:%d\n",pthread_self(),newNode->number);
pthread_mutex_unlock(&mutex);
//生产了任务,通知消费者来消费啦!
pthread_cond_broadcast(&cond);
//生产的慢一点哦
sleep(rand()%3);//0 1 2
}
return NULL;
}
//消费者
//消费者有下限,如果链表为空,就要阻塞消费者线程
void* consumer(void*arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
// 一直消费, 删除链表中的一个节点
//if(head == NULL)有bug:
/*当任务队列为空, 所有的消费者线程都会被这个函数阻塞 pthread_cond_wait(&cond, &mutex);
当生产者生产了1个节点, 调用 pthread_cond_broadcast(&cond); 唤醒了所有阻塞的线程
- 有一个消费者线程通过 pthread_cond_wait()加锁成功, 其余没有加锁成功的线程继续阻塞
- 加锁成功的线程向下运行, 并成功删除一个节点, 然后解锁
- 没有加锁成功的线程解除阻塞继续抢这把锁, 另外一个子线程加锁成功
- 但是这个线程删除链表节点的时候链表已经为空了, 后边访问这个空节点的时候就会出现段错误
解决方案:
- 需要循环的对链表是否为空进行判断, 需要将if 该成 while
*/
while(head==NULL)
{
/*
线程加互斥锁成功,但是线程阻塞在这行代码上,
锁还没解开,其他线程在访问这把互斥锁的时候也会被阻塞在这把互斥锁上,生产者也会被阻塞,造成死锁
但是并没有死锁,why?
pthread_cond_wait()此函数内部会自动将线程拥有的锁2.解开
当消费者线程解除阻塞之后,会自动将这把锁3.锁上,
这时候当前这个线程又重新拥有了这把互斥锁
*/
//1.阻塞消费者线程
pthread_cond_wait(&cond,&mutex);
}
//链表的头结点,将其删除
struct Node*node=head;
printf("消费者,id:%ld ,number:%d\n",pthread_self(),node->number);
head=node->next;
free(node);
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
return NULL;
}
int main()
{
//初始化
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
//创建两个数组,这两个数组里面来存储子线程创建出来的ID
pthread_t t1[5],t2[5];
for(int i=0;i<5;i++){
pthread_create(&t1[i],NULL,producer,NULL);
}
for(int i=0;i<5;i++){
pthread_create(&t2[i],NULL,consumer,NULL);
}
//线程资源的释放
for(int i=0;i<5;i++){
//阻塞等待子线程的退出
pthread_join(t1[i],NULL);
pthread_join(t2[i],NULL);
}
//销毁条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
输出结果,因为使用了while()会一直的生产
信号量
信号量和条件变量一样用于处理生产者和消费者模型,用于阻塞生产者线程或消费者线程的运行,信号的类型为sem_t对应的头文件为<semaphore.h>
#include<semaphore.h>
sem_t sem;
#include<semaphore.h>
//初始化信号量/信号灯
int sem_init(sem_t*sem,int pshared,unsigned int value);
//资源释放,线程销毁之后调用这个函数即可
//参数sem就是sem_init()的第一个参数
int sem_destroy(sem_t *sem);
参数:
sem:信号量变量地址
pshared:
0:线程同步
非0:进程同步
value:初始化当前信号量拥有的资源数(>=0),如果资源数为0,线程就会被阻塞了
//参数sem就是sem_init()的第一个参数
//函数被调用sem中的资源就会被销毁1个,资源数-1
int sem_wait(sem_t *sem);
当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,因此线程也就被阻塞了。
//参数sem就是sem_init()的第一个参数
//函数被调用sem中的资源就会被销毁1个,资源数-1
int sem_trywait(sem_t *sem);
当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,但是线程不会被阻塞,直接返回错误号,因此可以在程序中添加判断分支,用于处理获取资源失败之后的情况。
// 表示的时间是从1970.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 调用该函数线程获取sem中的一个资源,当资源数为0时,线程阻塞,在阻塞abs_timeout对应的时长之后,解除阻塞。
// abs_timeout: 阻塞的时间长度, 单位是s, 是从1970.1.1开始计算的
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
该函数的参数 abs_timeout 和 pthread_cond_timedwait 的最后一个参数是一样的,使用方法不再过多赘述。当线程调用这个函数,并且 sem 中的资源数 >0,线程不会阻塞,线程会占用 sem 中的一个资源,因此资源数 - 1,直到 sem 中的资源数减为 0 时,资源被耗尽,线程被阻塞,当阻塞指定的时长之后,线程解除阻塞。
// 调用该函数给sem中的资源数+1
int sem_post(sem_t *sem);
用该函数会将 sem 中的资源数 +1,如果有线程在调用 sem_wait、sem_trywait、sem_timedwait 时因为 sem 中的资源数为 0 被阻塞了,这时这些线程会解除阻塞,获取到资源之后继续向下运行。
// 查看信号量 sem 中的整形数的当前值, 这个值会被写入到sval指针对应的内存中
// sval是一个传出参数
int sem_getvalue(sem_t *sem, int *sval);
通过这个函数可以查看 sem 中现在拥有的资源个数,通过第二个参数 sval 将数据传出,也就是说第二个参数的作用和返回值是一样的。
通过信号量实现生产者消费者模型
场景一:(总资源数为1)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
//生产者的信号量
sem_t semp;
//消费者的信号量
sem_t semc;
//创建互斥锁
pthread_mutex_t mutex;
//链表节点类型
struct Node
{
int number;//节点数据值
struct Node*next;//指向当前节点类型后继节点的地址
};
//创建头结点
struct Node*head=NULL;
//生产者
//此处用的是链表没有上限
void* producer(void* arg)
{
//一直生产
while(1)
{
sem_wait(&semp);//判断semp里面的资源数为0的时候,所有的生产者都阻塞在这里了
//创建新节点
struct Node*newNode=(struct Node*)malloc(sizeof(struct Node));
//初始化节点
newNode->number=rand()%1000;//获取随机数,1000以内
//节点的连接,添加到链表的头部,新节点就是新的头结点
newNode->next=head;
//head指针前移
head=newNode;
printf("生产者,id:%ld ,number:%d\n",pthread_self(),newNode->number);
sem_post(&semc);//生产者生产了就通知消费者来消费啦
//生产的慢一点哦
sleep(rand()%3);//0 1 2
}
return NULL;
}
//消费者
//消费者有下限,如果链表为空,就要阻塞消费者线程
void* consumer(void*arg)
{
while(1)
{
sem_wait(&semc);
//链表的头结点,将其删除
struct Node*node=head;
printf("消费者,id:%ld ,number:%d\n",pthread_self(),node->number);
head=node->next;
free(node);
sem_post(&semp);//通知生产者生产
sleep(rand()%3);
}
return NULL;
}
int main()
{
//生产者
sem_init(&semp,0,1);//资源总数为1
//消费者->刚开始不能消费,资源数量初始化为0,消费者线程启动就阻塞了
sem_init(&semc,0,0);
//初始化
pthread_mutex_init(&mutex,NULL);
//创建两个数组,这两个数组里面来存储子线程创建出来的ID
pthread_t t1[5],t2[5];
for(int i=0;i<5;i++){
pthread_create(&t1[i],NULL,producer,NULL);
}
for(int i=0;i<5;i++){
pthread_create(&t2[i],NULL,consumer,NULL);
}
//线程资源的释放
for(int i=0;i<5;i++){
//阻塞等待子线程的退出
pthread_join(t1[i],NULL);
pthread_join(t2[i],NULL);
}
pthread_mutex_destroy(&mutex);
sem_destroy(&semp);
sem_destroy(&semc);
return 0;
}
通过测试代码可以得到如下结论:如果生产者和消费者使用的信号量总资源数为 1,那么不会出现生产者线程和消费者线程同时访问共享资源的情况,不管生产者和消费者线程有多少个,它们都是顺序执行的。
场景二:(总资源数大于一)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>
//生产者的信号量
sem_t semp;
//消费者的信号量
sem_t semc;
//创建互斥锁
pthread_mutex_t mutex;
//链表节点类型
struct Node
{
int number;//节点数据值
struct Node*next;//指向当前节点类型后继节点的地址
};
//创建头结点
struct Node*head=NULL;
//生产者
//此处用的是链表没有上限
void* producer(void* arg)
{
//一直生产
while(1)
{
//pthread_mutex_lock(&mutex);不能放在sem_wait的上面,会导致所有的生产者阻塞,还有消费者也会阻塞
sem_wait(&semp);//判断semp里面的资源数为0的时候,所有的生产者都阻塞在这里了
pthread_mutex_lock(&mutex);
//创建新节点
struct Node*newNode=(struct Node*)malloc(sizeof(struct Node));
//初始化节点
newNode->number=rand()%1000;//获取随机数,1000以内
//节点的连接,添加到链表的头部,新节点就是新的头结点
newNode->next=head;
//head指针前移
head=newNode;
printf("生产者,id:%ld ,number:%d\n",pthread_self(),newNode->number);
pthread_mutex_unlock(&mutex);
sem_post(&semc);//生产者生产了就通知消费者来消费啦
//生产的慢一点哦
sleep(rand()%3);//0 1 2
}
return NULL;
}
//消费者
//消费者有下限,如果链表为空,就要阻塞消费者线程
void* consumer(void*arg)
{
while(1)
{
//pthread_mutex_lock(&mutex);放在上面,如果下面这行代码判断为0然后阻塞,消费者就会阻塞在这里了
sem_wait(&semc);
pthread_mutex_lock(&mutex);
//链表的头结点,将其删除
struct Node*node=head;
printf("消费者,id:%ld ,number:%d\n",pthread_self(),node->number);
head=node->next;
free(node);
pthread_mutex_lock(&mutex);
sem_post(&semp);//通知生产者生产
sleep(rand()%3);
}
return NULL;
}
int main()
{
//生产者,最多有五个生产者可以同时运行,涉及到一个问题同时添加会出现问题,要线程同步了
//资源总数大于1了
sem_init(&semp,0,5);
//消费者->刚开始不能消费,资源数量初始化为0,消费者线程启动就阻塞了
sem_init(&semc,0,0);
//初始化
pthread_mutex_init(&mutex,NULL);
//创建两个数组,这两个数组里面来存储子线程创建出来的ID
pthread_t t1[5],t2[5];
for(int i=0;i<5;i++){
pthread_create(&t1[i],NULL,producer,NULL);
}
for(int i=0;i<5;i++){
pthread_create(&t2[i],NULL,consumer,NULL);
}
//线程资源的释放
for(int i=0;i<5;i++){
//阻塞等待子线程的退出
pthread_join(t1[i],NULL);
pthread_join(t2[i],NULL);
}
pthread_mutex_destroy(&mutex);
sem_destroy(&semp);
sem_destroy(&semc);
return 0;
}