Linux系统编程Day09

1.互斥锁

1.1 同步与互斥概述
	现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任
务操作系统中,同时运行的多个任务可能:
	1.都需要访问/使用同一种资源.
	2.多个任务之间有依赖关系,某个任务的运行依赖于另一个任务
	这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,
同步和互斥就是用于解决这两个问题的。

	互斥:是指散步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片
段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序
片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程
使用,多个进程或线程不能同时使用公共资源。
	同步:是指散步在不同任务之间的若干程序片断,它们的运行必须严格按照规定的
某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 
任务的运行依赖于 B 任务产生的数据。
	显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两
个任务之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才
能运行,而同步也是不能同时运行,但他是必须要按照某种次序来运行相应的线程(也
是一种互斥)!因此互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,即任
务是无序的,而同步的任务之间则有顺序关系。

1.2 互斥锁Mutex介绍
	在线程里也有这么一把锁:互斥锁(mutex),也叫互斥量,互斥锁是一种简单的
加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁( lock )和解锁( unlock )。

	互斥锁的操作流程如下:
	1)在访问共享资源后临界区域前,对互斥锁进行加锁。		
	2)在访问完成后释放互斥锁导上的锁。		
	3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到
	锁被释放。
	
	互斥锁的数据类型是: pthread_mutex_t。

1.3 pthread_mutex_init 函数
	初始化互斥锁:
	
	#include <pthread.h>
	​
	int pthread_mutex_init(pthread_mutex_t *restrict mutex,
	    const pthread_mutexattr_t *restrict attr);
	功能:
	    初始化一个互斥锁。
	参数:
	    mutex:互斥锁地址。类型是 pthread_mutex_t 。
	    attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
	​
	    可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:
	    pthread_mutex_t  mutex = PTHREAD_MUTEX_INITIALIZER;
	​
	这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查。
	​
	返回值:
	    成功:0,成功申请的锁默认是打开的。
	    失败:非 0 错误码

1.4 pthread_mutex_destroy函数
	#include <pthread.h>
	​
	int pthread_mutex_destroy(pthread_mutex_t *mutex);
	功能:
	    销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
	参数:
	    mutex:互斥锁地址。
	返回值:
	    成功:0
	    失败:非 0 错误码

1.5 pthread_mutex_lock函数
	#include <pthread.h>
	​
	int pthread_mutex_lock(pthread_mutex_t *mutex);
	功能:
	    对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
	参数:
	    mutex:互斥锁地址。
	返回值:
	    成功:0
	    失败:非 0 错误码
	​
	int pthread_mutex_trylock(pthread_mutex_t *mutex);
	   调用该函数时,若互斥锁未加锁,则上锁,返回 0;
	   若互斥锁已加锁,则函数直接返回失败,即 EBUSY。

1.6 pthread_mutex_unlock函数
	#include <pthread.h>
	​
	int pthread_mutex_unlock(pthread_mutex_t *mutex);
	功能:
	    对指定的互斥锁解锁。
	参数:
	    mutex:互斥锁地址。
	返回值:
	    成功:0
	    失败:非0错误码

1.7 测试程序
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

//互斥锁变量
pthread_mutex_t mutex;

//输出大写字母
void *fun1(void* arg)
{
	//加锁
	pthread_mutex_lock(&mutex);

	for(int i = 'A';i <= 'Z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}

	//解锁
	pthread_mutex_unlock(&mutex);

	return NULL;
}

//输出小写字母
void *fun2(void* arg)
{
	//加锁
	pthread_mutex_lock(&mutex);

	for(int i = 'a';i <= 'z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}

	//解锁
	pthread_mutex_unlock(&mutex);

	return NULL;
}

//模拟输出字母
int main()
{
	int ret = -1;
	pthread_t tid1,tid2;
	//初始化锁
	pthread_mutex_init(&mutex, NULL);

	//创建两个线程
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_create(&tid2, NULL, fun2, NULL);

	//等待线程结束
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);

	putchar('\n');
	printf("主进程结束\n");

	//销毁锁
	pthread_mutex_destroy(&mutex);
	return 0;
}

2. 死锁

2.1 死锁概念	
	死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而
造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁
状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

2.2 死锁引起的原因
	1.竞争不可抢占资源引起死锁,也就是我们说的第一种情况,而这都在等待对方占
	有的不可抢占的资源。
	2.竞争可消耗资源引起死锁
		有p1,p2,p3三个进程,p1向p2发送消息并接受p3发送的消息,p2向p3发送
	消息并接受p1的消息,p3向p1发送消息并接受p2的消息,如果设置是先接到消息
	后发送消息,则所有的消息都不能发送,这就造成死锁。
	3.进程推进顺序不当引起死锁
		有进程p1,p2,都需要资源A,B,本来可以p1运行A --> p1运行B --> p2运行A --> p2运行B,
	但是顺序换了,p1运行A时p2运行B,容易发生第一种死锁。互相抢占资源。

2.3 死锁的必要条件
	1.互斥条件		
	某资源只能被一个进程使用,其他进程请求该资源时,只能等待,直到资源使用完
	毕后释放资源。		
	2.请求和保持条件		
	程序已经保持了至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,
	自己占用资源却保持不放。		
	3.不可抢占条件		
	进程已获得的资源没有使用完,不能被抢占。		
	4.循环等待条件		
	必然存在一个循环链。

2.4 处理死锁
	1.预防死锁		
	破坏死锁的四个必要条件中的一个或多个来预防死锁。		
	2.避免死锁		
	和预防死锁的区别就是,在资源动态分配过程中,用某种方式防止系统进入不安全的
	状态。		
	3.检测死锁		
	运行时出现死锁,能及时发现死锁,把程序解脱出来		
	4.解除死锁		
	发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进
	程。

2.5 预防死锁的方法
1.破坏请求和保持条件	
协议1:	
	所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出
资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲
的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

协议2:	
	允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然
后再请求新的资源。

2.破坏不可抢占条件
	当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能被满足时,它必
须释放已经保持的所有资源,以后需要时再重新申请。

3.破坏循环等待条件
	对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺
序请求资源。假如进程请求到了一些序列号较高的资源,然后有请求一个序列较低的资
源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须
一起请求。

2.6 死锁示例
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

//线程互斥量变量
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;

void* fun1(void* arg)
{
	//线程1先申请资源1,再申请资源2
	pthread_mutex_lock(&mutex1);
	printf("线程1加锁资源1\n");
	pthread_mutex_lock(&mutex2);
	printf("线程1加锁资源2\n");

	printf("线程1执行函数!\n");

	//线程1先解锁资源1,再解锁资源2
	pthread_mutex_unlock(&mutex1);
	pthread_mutex_unlock(&mutex2);

	return NULL;
}

void* fun2(void* arg)
{

	//线程2先申请资源2,再申请资源1
	pthread_mutex_lock(&mutex2);
	printf("线程2加锁资源2\n");
	pthread_mutex_lock(&mutex1);
	printf("线程2加锁资源1\n");

	printf("线程2执行函数!\n");

	//线程2先解锁资源2,再解锁资源1
	pthread_mutex_unlock(&mutex2);
	pthread_mutex_unlock(&mutex1);

	return NULL;
}


int main()
{
	int ret = -1;
	
	//创建线程变量
	pthread_t tid1,tid2;

	//初始化互斥锁
	pthread_mutex_init(&mutex1, NULL);
	pthread_mutex_init(&mutex2, NULL);

	//创建线程
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_create(&tid2, NULL, fun2, NULL);

	//回收线程资源
	ret = pthread_join(tid1, NULL);
	if(0 != ret)
	{
		printf("线程资源回收失败\n");
		return 1;
	}
	ret = pthread_join(tid2, NULL);
	if(0 != ret)
	{
		printf("线程资源回收失败\n");
		return 1;
	}

	//互斥锁销毁
	pthread_mutex_destroy(&mutex1);
	pthread_mutex_destroy(&mutex2);

	return 0;
}

3. 读写锁

3.1 读写锁概述
	当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。
但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个
线程也想读取这个共享资源,但是由于互斥锁的排它性,所有其它线程都无法获取锁,
也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。
	在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写
应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁
来实现。

读写锁的特点如下:
	1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。		
	2)如果有其它线程写数据,则其它线程都不允许读、写操作。

读写锁分为读锁和写锁,规则如下:
	1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
	2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。

POSIX 定义的读写锁的数据类型是: pthread_rwlock_t。

3.2 pthread_rwlock_init函数
	#include <pthread.h>
	​
	int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
	    const pthread_rwlockattr_t *restrict attr);
	功能:
	    用来初始化 rwlock 所指向的读写锁。
	​
	参数:
	    rwlock:指向要初始化的读写锁指针。
	    attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化
	    读写锁,否则使用指定的 attr 初始化读写锁。
	​
	    可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:
	    pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;
	​
	    这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 
	    来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进
	    行错误检查。
	​
	返回值:
	    成功:0,读写锁的状态将成为已初始化和已解锁。
	    失败:非 0 错误码。

3.3 pthread_rwlock_destroy函数
	#include <pthread.h>
	​
	int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
	功能:
	    用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
	参数:
	    rwlock:读写锁指针。
	返回值:
	    成功:0
	    失败:非 0 错误码

3.4  pthread_rwlock_rdlock函数
	#include <pthread.h>
	​
	int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
	功能:
	    以阻塞方式在读写锁上获取读锁(读锁定)。
	    如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
	    如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。
	    线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。
	参数:
	    rwlock:读写锁指针。
	返回值:
	    成功:0
	    失败:非 0 错误码
	​
	int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
	1.用于尝试以非阻塞的方式来在读写锁上获取读锁。
	2.如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。

3.5 pthread_rwlock_wrlock函数
	#include <pthread.h>
	​
	int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
	功能:
	    在读写锁上获取写锁(写锁定)。
	    如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
	    如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
	参数:
	    rwlock:读写锁指针。
	返回值:
	    成功:0
	    失败:非 0 错误码
	​
	int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
	1.用于尝试以非阻塞的方式来在读写锁上获取写锁。
	2.如果有任何的读者或写者持有该锁,则立即失败返回。

3.6 pthread_rwlock_unlock函数
	#include <pthread.h>
	​
	int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
	功能:
	    无论是读锁或写锁,都可以通过此函数解锁。
	参数:
	    rwlock:读写锁指针。
	返回值:
	    成功:0
	    失败:非 0 错误码

3.7 测试程序示例

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>

int num = 0;
//创建读写锁变量
pthread_rwlock_t rwlock;

//读线程
void* fun_read(void* arg)
{
	int index = (int)(long)arg;

	while(1)
	{
		//加读写锁的读锁
		
		pthread_rwlock_rdlock(&rwlock);

		printf("%d号进程这在读取,读取内容 num = %d\n", index, num);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand() % 3 + 1);
		
	}

	return NULL;
}

//写线程
void* fun_write(void* arg)
{
	int index = (int)(long)arg;

	while(1)
	{
		//加读写锁的写锁
		pthread_rwlock_wrlock(&rwlock);

		num++;
		printf("%d号进程 修改num = %d\n", index, num);

		//解锁
		pthread_rwlock_unlock(&rwlock);
		sleep(rand() % 3 + 1);
	}


	return NULL;
}


int main()
{
	int ret = -1;
	srand((unsigned int)time(NULL));

	//初始化读写锁
	ret = pthread_rwlock_init(&rwlock, NULL);
	if(0 != ret)
	{
		printf("创建读写锁失败!\n");
		return 1;
	}

	//创建8个线程
	pthread_t tid[8];

	for(int i = 0; i < 8; i++)
	{
		if(i < 5)
		{
			pthread_create(&tid[i], NULL, fun_read, (void* )(long)i);
		}
		else
		{
			pthread_create(&tid[i], NULL, fun_write, (void* )(long)i);
		}
	}

	//回收8个线程的资源
	for(int i = 0; i < 8; i++)
	{
		pthread_join(tid[i], NULL);
	}

	//销毁读写锁
	pthread_rwlock_destroy(&rwlock);

	return 0;
}

4. 条件变量

4.1 条件变量概述
	与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!
	条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁
同时使用。
条件变量的两个动作:
	1.条件不满, 阻塞线程
	2.当条件满足, 通知阻塞的线程开始工作
	
条件变量的类型: pthread_cond_t。

4.2 pthread_cond_init函数
	#include <pthread.h>
	​
	int pthread_cond_init(pthread_cond_t *restrict cond,
	    const pthread_condattr_t *restrict attr);
	功能:
	    初始化一个条件变量
	参数:
	    cond:指向要初始化的条件变量指针。
	    attr:条件变量属性,通常为默认值,传NULL即可
	        也可以使用静态初始化的方法,初始化条件变量:
	        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
	返回值:
	    成功:0
	    失败:非0错误号

4.3  pthread_cond_destroy函数
	#include <pthread.h>
	​
	int pthread_cond_destroy(pthread_cond_t *cond);
	功能:
	    销毁一个条件变量
	参数:
	    cond:指向要初始化的条件变量指针
	返回值:
	    成功:0
	    失败:非0错误号

4.4 pthread_cond_wait函数
	#include <pthread.h>		​
	int pthread_cond_wait(pthread_cond_t *restrict cond,
	    pthread_mutex_t *restrict mutex);
	功能:
	    阻塞等待一个条件变量
	    a) 阻塞等待条件变量cond(参1)满足
	    b) 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
	           注: a) b) 两步为一个原子操作。
	    c) 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取
	    互斥锁pthread_mutex_lock(&mutex);		​
	参数:
	    cond:指向要初始化的条件变量指针
	    mutex:互斥锁		​
	返回值:
	    成功:0
	    失败:非0错误号
	​
	int pthread_cond_timedwait(pthread_cond_t *restrict cond,
	    pthread_mutex_t *restrict mutex,
	    const struct*restrict abstime);
	功能:
	    限时等待一个条件变量
	​
	参数:
	    cond:指向要初始化的条件变量指针
	    mutex:互斥锁
	    abstime:绝对时间
	​
	返回值:
	    成功:0
	    失败:非0错误号

abstime补充说明:
	struct timespec 
	{
	    time_t tv_sec;      /* seconds */ // 秒
	    long   tv_nsec; /* nanosecondes*/ // 纳秒
	}
	​
	time_t cur = time(NULL);        //获取当前时间。
	struct timespec t;              //定义timespec 结构体变量t
	t.tv_sec = cur + 1;             // 定时1秒
	pthread_cond_timedwait(&cond, &t);

示例代码如下所示:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

//设置条件变量属性
pthread_cond_t cond;
pthread_mutex_t mutex;
int flag = 0;

//改变条件的线程
void* fun1(void* arg)
{
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);
		flag = 1;
		//解锁
		pthread_mutex_unlock(&mutex);

		//唤醒因为条件而阻塞的线程
		pthread_cond_signal(&cond);
		
		sleep(2);
	}

	return NULL;
}

void* fun2(void* arg)
{

	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		//表示条件不满足
		if(0 == flag)
		{
			pthread_cond_wait(&cond, &mutex);
		}

		printf("线程二因为条件满足开始运行!\n");
		flag = 0;

		//解锁
		pthread_mutex_unlock(&mutex);
	}

	return NULL;
}

//条件变量的应用
int main()
{
	int ret = -1;
	pthread_t tid1,tid2;

	//初始化条件变量
	ret = pthread_cond_init(&cond, NULL);
	if(0 != ret)
	{
		printf("初始化条件变量失败!\n");
		return 1;
	}

	//初始化互斥量
	ret = pthread_mutex_init(&mutex, NULL);
	if(0 != ret)
	{
		printf("初始化互斥量失败!\n");
		return 1;
	}

	//创建两个线程
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_create(&tid2, NULL, fun2, NULL);

	//回收线程资源
	ret = pthread_join(tid1, NULL);
	if(0 != ret)
	{
		printf("线程资源回收失败!\n");
		return 1;
	}
	ret = pthread_join(tid2, NULL);
	if(0 != ret)
	{
		printf("线程资源回收失败!\n");
		return 1;
	}

	//销毁互斥量
	pthread_mutex_destroy(&mutex);

	//销毁条件变量
	pthread_cond_destroy(&cond);

	return 0;
}

4.5 pthread_cond_signal函数
	唤醒至阻塞在条件变量上的线程
	
	#include <pthread.h>		​
	int pthread_cond_signal(pthread_cond_t *cond);
	功能:
	    唤醒至少一个阻塞在条件变量上的线程
	参数:
	    cond:指向要初始化的条件变量指针
	返回值:
	    成功:0
	    失败:非0错误号
	​
	int pthread_cond_broadcast(pthread_cond_t *cond);
	功能:
	    唤醒全部阻塞在条件变量上的线程
	参数:
	    cond:指向要初始化的条件变量指针
	返回值:
	    成功:0
	    失败:非0错误号

4.6 生产者消费者条件变量模型
	线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是
比较常见的一种方法。
	假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作
一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>

//链表结点
struct node
{
	int data;
	struct node* pNext;
};

//条件变量
pthread_cond_t cond;
//互斥量
pthread_mutex_t mutex;

struct node* head = NULL;

void* producer(void* arg)
{
	//生产者循环生产产品
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		struct node* new = (struct node*)malloc(sizeof(struct node));
		if(NULL == new)
		{
			printf("空间开辟失败!\n");
			break;
		}
		memset(new, 0, sizeof(struct node));

		new->data = rand() % 100 + 1;
		new->pNext = NULL;

		new->pNext = head;
		head = new;

		printf("生产者生产:%d\n", head->data);

		//解锁
		pthread_mutex_unlock(&mutex);

		//唤醒因此被堵塞的信号
		pthread_cond_signal(&cond);

		sleep(rand() % 3 + 1);

	}

	pthread_exit(NULL);
}

void* cousumer(void* arg)
{
	struct node* temp = NULL;
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		if(NULL == head)
		{
			//printf("当前生产内容为空..\n");
			//如果链表为空,就阻塞
			pthread_cond_wait(&cond, &mutex);
		}
		else
		{
			//此时不可以进行深拷贝,是因为加入深拷贝的话指向的就是不同的空间了
			//这样的话,就不能通过临时的指针来操作之前所开辟好的空间了
			//temp->data = head->data;
			//temp->pNext = head->pNext;

			temp = head;
			head = head->pNext;

			printf("消费者消费:%d\n", temp->data);
			free(temp);
		}

		//解锁
		pthread_mutex_unlock(&mutex);
	}

	pthread_exit(NULL);
}


//生产者和消费者模型
int main()
{
	srand((unsigned int)time(NULL));
	int ret = -1;
	pthread_t tid1 = -1,tid2 = -1;
	
	//初始化条件变量
	ret = pthread_cond_init(&cond, NULL);
	if(0 != ret)
	{
		printf("条件变量初始化失败!\n");
		return 1;
	}

	//初始化互斥量
	ret = pthread_mutex_init(&mutex, NULL);
	if(0 != ret)
	{
		printf("互斥零初始化失败!\n");
		return 1;
	}

	//创建线程
	pthread_create(&tid1, NULL, producer, NULL);
	pthread_create(&tid2, NULL, cousumer, NULL);


	//回收线程资源
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);

	//销毁条件变量和互斥量
	pthread_cond_destroy(&cond);
	pthread_mutex_destroy(&mutex);

	return 0;
}
4.7 条件变量的优缺点
	相较于mutex而言,条件变量可以减少竞争。
	如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要
竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。
	有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了
程序效率。

5. 信号量

5.1 信号量概述
	信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数
器,它被用来控制对公共资源的访问。
	编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量
值大于 0 时,则可以访问,否则将阻塞。
	PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。
	信号量主要用于进程或线程间的同步和互斥这两种典型情况。
	信号量数据类型为:sem_t。

5.2 sem_init函数
初始化信号量:		
	#include <semaphore.h>		​
	int sem_init(sem_t *sem, int pshared, unsigned int value);
	功能:
	    创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
	参数:
	    sem:信号量的地址。
	    pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
	    value:信号量的初始值。
	返回值:
	    成功:0
	    失败: - 1

5.3 sem_destroy函数
销毁信号量:
	#include <semaphore.h>		​
	int sem_destroy(sem_t *sem);
	功能:
	    删除 sem 标识的信号量。
	参数:
	    sem:信号量地址。
	返回值:
	    成功:0
	    失败: - 1

5.4 信号量P操作(减1)
	#include <semaphore.h>​
	int sem_wait(sem_t *sem);
	功能:
	    将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,此函数会阻塞,直到信号量大于 0 时才进行减 1 操作。
	参数:
	    sem:信号量的地址。
	返回值:
	    成功:0
	    失败: - 1
	​
	int sem_trywait(sem_t *sem);
	   以非阻塞的方式来对信号量进行减 1 操作。
	   若操作前,信号量的值等于 0,则对信号量的操作失败,函数立即返回。
	​
	int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
	   限时尝试将信号量的值减 1
	   abs_timeout:绝对时间​
	
abs_timeout补充说明:
	struct timespec {
	    time_t tv_sec;      /* seconds */ // 秒
	    long   tv_nsec; /* nanosecondes*/ // 纳秒
	}
	​
	time_t cur = time(NULL);        //获取当前时间。
	struct timespec t;              //定义timespec 结构体变量t
	t.tv_sec = cur + 1;             // 定时1秒
	sem_timedwait(&cond, &t);

5.5 信号量V操作(加1)
	#include <semaphore.h>	​
	int sem_post(sem_t *sem);
	功能:
	    将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。
	参数:
	    sem:信号量的地址。
	返回值:
	    成功:0
	    失败:-1 

5.6 获取信号量的值
	#include <semaphore.h>		​
	int sem_getvalue(sem_t *sem, int *sval);
	功能:
	    获取 sem 标识的信号量的值,保存在 sval 中。
	参数:
	    sem:信号量地址。
	    sval:保存信号量值的地址。
	返回值:
	    成功:0
	    失败:-1

5.7 程序示例(使用信号量实现打印机功能)
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

//信号量变量
sem_t sem;

//输出大写字母
void *fun1(void* arg)
{
	//申请资源(p操作)
	sem_wait(&sem);

	for(int i = 'A';i <= 'Z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}

	//释放资源(v操作)
	sem_post(&sem);

	return NULL;
}

//输出小写字母
void *fun2(void* arg)
{
	
	//申请资源(p操作)
	sem_wait(&sem);
	for(int i = 'a';i <= 'z';i++)
	{
		putchar(i);
		fflush(stdout);
		usleep(100000);
	}

	//释放资源(v操作)
	sem_post(&sem);

	return NULL;
}

//模拟输出字母
int main()
{
	int ret = -1;
	pthread_t tid1,tid2;
	//初始化信号量
	ret = sem_init(&sem, 0, 1);
	if(0 != ret)
	{
		printf("初始化信号量失败!\n");
		return 1;
	}
	printf("初始化信号量成功!\n");

	//创建两个线程
	pthread_create(&tid1, NULL, fun1, NULL);
	pthread_create(&tid2, NULL, fun2, NULL);

	//等待线程结束
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);

	putchar('\n');
	printf("主进程结束\n");

	//销毁信号量
	sem_destroy(&sem);

	return 0;
}

5.8 使用信号量实现(多进程)生产者和消费者模型
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
#include<semaphore.h>

//链表结点
struct node
{
	int data;
	struct node* pNext;
};


struct node* head = NULL;

//信号量变量
sem_t sem_producer;
sem_t sem_cousumer;


void* producer(void* arg)
{
	//生产者循环生产产品
	while(1)
	{
		//申请一个资源
		sem_wait(&sem_producer);

		struct node* new = (struct node*)malloc(sizeof(struct node));
		if(NULL == new)
		{
			printf("空间开辟失败!\n");
			break;
		}
		memset(new, 0, sizeof(struct node));

		new->data = rand() % 100 + 1;
		new->pNext = NULL;

		new->pNext = head;
		head = new;

		printf("生产者生产:%d\n", head->data);

		//释放一个资源给消费者
		sem_post(&sem_cousumer);

		sleep(rand() % 3 + 1);

	}

	pthread_exit(NULL);
}

void* cousumer(void* arg)
{
	struct node* temp = NULL;
	while(1)
	{
		//申请一个资源
		sem_wait(&sem_cousumer);

		if(NULL == head)
		{
			printf("当前生产内容为空..\n");
		}
		
	

		temp = head;
		head = head->pNext;

		printf("消费者消费:%d\n", temp->data);
		free(temp);

		//释放一个资源给生产者
		sem_post(&sem_producer);
	}

	pthread_exit(NULL);
}


//生产者和消费者模型
int main()
{
	srand((unsigned int)time(NULL));
	int ret = -1;
	pthread_t tid[6];

	//初始化信号量
	ret = sem_init(&sem_producer, 0, 6);
	if(0 != ret)
	{
		printf("信号量初始化失败!\n");
		return 1;
	}

	ret = sem_init(&sem_cousumer, 0, 0);
	if(0 != ret)
	{
		printf("信号量初始化失败!\n");
		return 0;
	}
	
	//创建线程
	for(int i = 0; i < 6; i++)
	{
		//生产者
		if(i < 2)
		{

			pthread_create(&tid[i], NULL, producer, NULL);
		}
		else
		{
			//消费者
			pthread_create(&tid[i], NULL, cousumer, NULL);
		}
	}


	//回收线程资源
	for(int i = 0;i < 6; i++)
	{
		pthread_join(tid[i], NULL);
	}

	//销毁
	sem_destroy(&sem_producer);
	sem_destroy(&sem_cousumer);

	return 0;
}

6 哲学家就餐问题

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
​
pthread_mutex_t mutex[5];void* dine(void* arg)
{
    int num = (int)arg;
    int left, right;if(num < 4)
    {
        // 前4个人,右手拿自己筷子
        right = num;
        left = num+1;
    }
    else if(num == 4)
    {
        // 最后一个人,右手拿别人筷子
        right = 0;
        left = num;
    }// 吃饭
    while(1)
    {
        // 右手加锁
        pthread_mutex_lock(&mutex[right]);
        // 尝试抢左手筷子
        if(pthread_mutex_trylock(&mutex[left]) == 0)
        {
            // 吃面。。。
            printf("%c 正在吃面。。。。。。\n", num+'A');
            // 吃完放筷子
            pthread_mutex_unlock(&mutex[left]);
        }
        // 解锁
        pthread_mutex_unlock(&mutex[right]);
        sleep(rand()%5);
    }
}int main(int argc, const char* argv[])
{
    pthread_t p[5];for(int i=0; i<5; ++i)
    {
        pthread_mutex_init(&mutex[i], NULL);
    }for(int i=0; i<5; ++i)
    {
        pthread_create(&p[i], NULL, dine, (void*)i);
    }for(int i=0; i<5; ++i)
    {
        pthread_join(p[i], NULL);
    }
    
    for(int i=0; i<5; ++i)
    {
        pthread_mutex_destroy(&mutex[i]);
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux系统上配置CLion,首先需要安装相应的编译工具包。在Ubuntu中,可以使用以下命令进行安装: ``` sudo apt-get install build-essential ``` 这个命令将安装gcc(C编译器)、g++(C++编译器)和make(连接工具)。 接下来,如果在首次安装CLion时遇到报错,如"CMake Error: CMake was unable to find a build program corresponding to 'Unix Makefiles'.",则需要设置CMake的构建工具。可以通过以下步骤来解决这个问题: 1. 打开CLion,进入File菜单,选择Settings。 2. 在弹出的窗口中,找到Build, Execution, Deployment选项,然后选择CMake。 3. 在CMake的设置中,选择"Unix Makefiles"作为构建工具。 4. 保存设置并重新加载项目。 此外,如果在CLion中使用Cppcheck进行代码静态分析,需要配置Cppcheck的路径。在Linux系统上,可以通过以下步骤来进行配置: 1. 在CLion中,进入File菜单,选择Settings。 2. 在弹出的窗口中,找到Tools选项,然后选择Cppcheck。 3. 在Cppcheck设置中,将cppcheck的路径设置为正确的路径,例如/usr/bin/cppcheck。 4. 保存设置。 通过以上步骤,您可以在Linux系统上成功配置CLion,并使用gcc、g++、make和Cppcheck进行编程和代码分析。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [linux clion 编译环境配置](https://blog.csdn.net/whatday/article/details/131155382)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [clion-cppcheck:CLion的cppcheck插件](https://download.csdn.net/download/weixin_42131414/19111962)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值