线程同步

线程同步

同步

线程同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次访问

同步的方式

多个线程访问共享资源就需要线程同步,四种方法:互斥锁,读写锁,条件变量,信号量

//加锁
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;
}

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值