Linux系统编程——线程(2)

# Linux系统编程——线程(2)

前情提要: Linux系统编程——线程(1)


同步概念

​ 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。

​ 而编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。

线程同步

​ 同步即协同步调,按预定的先后次序运行。

​ 线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。

举例:

#include <func.h>
//主线程与子线程各加1000万
#define N 10000000
void* threadFunc(void *p)
{
	int *num=(int*)p;
	int i;
	for(i=0;i<N;i++)
	{
		*num+=1;
	}
	printf("I am child thread\n");
	return NULL;
}

int main()
{
	pthread_t pthID;//线程ID
	int ret;
	int num=0;
	ret=pthread_create(&pthID,NULL,threadFunc,&num);
	if(ret!=0)
	{
		printf("pthread_create:%s\n",strerror(ret));
		return -1;
	}
	int i;
	for(i=0;i<N;i++)
	{
		num=num+1;
	}
	pthread_join(pthID,NULL);
	printf("I am main thread,%d\n",num);
	return 0;
}

1556789829146

​ “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。

​ 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。

数据混乱原因:

  1. 资源共享(独享资源则不会)

  2. 调度随机(意味着数据访问会出现竞争)

  3. 线程间缺乏必要的同步机制。

​ 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。

​ 所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。

互斥量mutex

Linux中提供一把互斥锁mutex(也称之为互斥量)。

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。

资源还是共享的,线程间也还是竞争的。

pthread_mutex_init函数

pthread_mutex_destroy函数

pthread_mutex_lock函数

pthread_mutex_trylock函数

pthread_mutex_unlock函数

以上5个函数的返回值都是:成功返回0, 失败返回错误号

pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。

pthread_mutex_t mutex; 变量mutex只有两种取值1、0。

pthread_mutex_init函数

初始化一个互斥锁(互斥量) ---> 初值可看作1

​ int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参1:传出参数,调用时应传 &mutex

restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改

参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性

  1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;

  2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)

pthread_mutex_destroy函数

销毁一个互斥锁

​ int pthread_mutex_destroy(pthread_mutex_t *mutex);

#include <func.h>

int main()
{
	pthread_mutex_t mutex;
	int ret;
	ret=pthread_mutex_init(&mutex,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
	ret=pthread_mutex_destroy(&mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
	return 0;
}

pthread_mutex_lock函数

加锁。可理解为将mutex--(或-1)

​ int pthread_mutex_lock(pthread_mutex_t *mutex);

#include <func.h>

int main()
{
	pthread_mutex_t mutex;
	int ret;
	ret=pthread_mutex_init(&mutex,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
	pthread_mutex_lock(&mutex);
	pthread_mutex_lock(&mutex);//会阻塞
	printf("you can't see me\n");
	ret=pthread_mutex_destroy(&mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
	return 0;
}

pthread_mutex_unlock函数

解锁。可理解为将mutex ++(或+1)

​ int pthread_mutex_unlock(pthread_mutex_t *mutex);

主线程与子线程实现对同一变量的加法运算:

#include <func.h>
//主线程与子线程各加1000万
#define N 10000000
typedef struct{
	int num;
	pthread_mutex_t mutex;
}Data;
void* threadFunc(void *p)
{
	Data *pThreadInfo=(Data *)p;
	int i;
	for(i=0;i<N;i++)
	{
		pthread_mutex_lock(&pThreadInfo->mutex);
		pThreadInfo->num+=1;
		pthread_mutex_unlock(&pThreadInfo->mutex);
	}
	printf("I am child thread\n");
	return NULL;
}

int main()
{
	pthread_t pthID;//线程ID
	int ret;
	Data threadInfo;
	threadInfo.num=0;
	ret=pthread_mutex_init(&threadInfo.mutex,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
	struct timeval start,end;
	gettimeofday(&start,NULL);
	ret=pthread_create(&pthID,NULL,threadFunc,&threadInfo);
	if(ret!=0)
	{
		printf("pthread_create:%s\n",strerror(ret));
		return -1;
	}
	int i;
	for(i=0;i<N;i++)
	{
		pthread_mutex_lock(&threadInfo.mutex);
		threadInfo.num+=1;
		pthread_mutex_unlock(&threadInfo.mutex);
	}
	pthread_join(pthID,NULL);
	gettimeofday(&end,NULL);
	ret=pthread_mutex_destroy(&threadInfo.mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
	printf("I am main thread,%d,use time=%ld\n",threadInfo.num,(end.tv_sec-start.tv_sec)*1000000+end.tv_usec-start.tv_usec);
	return 0;
}

1556798202146

pthread_mutex_trylock函数

尝试加锁

​ int pthread_mutex_trylock(pthread_mutex_t *mutex);

#include <func.h>
//trylock尝试加锁
int main()
{
	pthread_mutex_t mutex;
	int ret;
	ret=pthread_mutex_init(&mutex,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
	pthread_mutex_lock(&mutex);
	ret=pthread_mutex_trylock(&mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_trylock");
	ret=pthread_mutex_destroy(&mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
	return 0;
}

1556798652450

线程加锁后被取消

​ 线程取消的方法是一个线程向目标线程发cancel 信号,但是如何处理cancel 信号则由目标线程自己决

定,目标线程或者忽略、或者立即终止、或者继续运行至cancelation-point(取消点)后终止。

线程为了访问临界共享资源而为其加上锁,但在访问过程中该线程被外界取消,或者发生了中断,则该

临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于

资源释放的编程。

​ 在POSIX 线程API 中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释

放资源,从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行

pthread_cleanup_push()所指定的清理函数。

函数原型:

void pthread_cleanup_push(void (*routine) (void *), void *arg)

void pthread_cleanup_pop(int execute)

pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,并且必须成对出现

void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对

pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反

顺序弹出。execute 参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,

为0 表示不执行,非0 为执行;这个参数并不影响异常终止时清理函数的执行。

#include <func.h>
void cleanup(void *p)
{
	pthread_mutex_unlock((pthread_mutex_t*)p);
	printf("unlock success\n");
}
//线程加锁以后被cancel怎么办
void* threadFunc(void *p)
{
	pthread_mutex_t* pMutex=(pthread_mutex_t*)p;
	pthread_cleanup_push(cleanup,pMutex); //指定清理函数cleanup
	pthread_mutex_lock(pMutex);
	sleep(3);
	pthread_cleanup_pop(1);
	pthread_exit(NULL);
}
int main()
{
	pthread_mutex_t mutex;
	int ret;
	ret=pthread_mutex_init(&mutex,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_init");
	pthread_t pthId[2];
	int i;
	for(i=0;i<2;i++)
	{
		pthread_create(pthId+i,NULL,threadFunc,&mutex);
	}
	for(i=0;i<2;i++)
	{
		ret=pthread_cancel(pthId[i]);
		THREAD_ERROR_CHECK(ret,"pthread_cancel");
	}
	for(i=0;i<2;i++)
	{
		pthread_join(pthId[i],NULL);
	}
	ret=pthread_mutex_destroy(&mutex);
	THREAD_ERROR_CHECK(ret,"pthread_mutex_destroy");
	return 0;
}

1556799671219

多线程售票功能(互斥锁)

#include <func.h>

typedef struct{
	int tickets;
	pthread_mutex_t mutex;
}Train;
void* saleWindows1(void* p)
{
	Train *pSale=(Train*)p;
	int count=0;
	while(1)
	{
		pthread_mutex_lock(&pSale->mutex);
		if(pSale->tickets>0)
		{
			//printf("I am saleWindows1,the tickets is %d\n",pSale->tickets);
			pSale->tickets--;
			count++;
			//printf("sale finish,I am saleWindows1,the tickets is %d\n",pSale->tickets);
			pthread_mutex_unlock(&pSale->mutex);
		}else{
			pthread_mutex_unlock(&pSale->mutex);
			printf("I am windows1 sale %d\n",count);
			break;
		}
	}
	return NULL;
}
void* saleWindows2(void* p)
{
	Train *pSale=(Train*)p;
	int count=0;
	while(1)
	{
		pthread_mutex_lock(&pSale->mutex);
		if(pSale->tickets>0)
		{
			//printf("I am saleWindows2,the tickets is %d\n",pSale->tickets);
			pSale->tickets--;
			count++;
			//printf("sale finish,I am saleWindows2,the tickets is %d\n",pSale->tickets);
			pthread_mutex_unlock(&pSale->mutex);
		}else{
			pthread_mutex_unlock(&pSale->mutex);
			printf("I am windows2 sale %d\n",count);
			break;
			break;
		}
	}
	return NULL;
}

typedef void* (*threadFunc)(void*);
int main()
{
	Train t;
	pthread_t pthId[2];
	int i;
	t.tickets=20000000;
	threadFunc pthreadFunc[2]={saleWindows1,saleWindows2};
	pthread_mutex_init(&t.mutex,NULL);
	for(i=0;i<2;i++)
	{
		pthread_create(pthId+i,NULL,pthreadFunc[i],&t);
	}
	for(i=0;i<2;i++)
	{
		pthread_join(pthId[i],NULL);
	}
	printf("sale over\n");
}

条件变量

​ 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待条件变

量的条件成立而挂起;另一个线程使条件成立(给出条件成立信号)。为了防止竞争,条件变量的使用总是

和一个互斥锁结合在一起。

​ pthread_cond_init函数

​ pthread_cond_destroy函数

​ pthread_cond_wait函数

​ pthread_cond_timedwait函数

​ pthread_cond_signal函数

​ pthread_cond_broadcast函数

以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

​ pthread_cond_t类型 用于定义条件变量

​ pthread_cond_t cond;

pthread_cond_init函数

初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

参2:attr表条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_destroy函数

销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_wait函数(※)

阻塞等待一个条件变量

​ int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

函数作用:

  1. 阻塞等待条件变量cond(参1)满足

  2. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);

1.2.两步为一个原子操作。

  1. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

先举一个简单的例子:

#include <func.h>
typedef struct{
	pthread_cond_t cond;
	pthread_mutex_t mutex;
}Data;
void* threadFunc(void* p)
{
	Data* pthreadInfo=(Data*)p;
	int ret;
	pthread_mutex_lock(&pthreadInfo->mutex);
	ret=pthread_cond_wait(&pthreadInfo->cond,&pthreadInfo->mutex);
	pthread_mutex_unlock(&pthreadInfo->mutex);
	printf("I am child,after wait\n");
	pthread_exit(NULL);
}
int main()
{
	Data threadInfo;
	int ret;
	ret=pthread_cond_init(&threadInfo.cond,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_cond_init");
	pthread_mutex_init(&threadInfo.mutex,NULL);
	pthread_t pthId;
	pthread_create(&pthId,NULL,threadFunc,&threadInfo);
	sleep(1);
	ret=pthread_cond_signal(&threadInfo.cond);
	THREAD_ERROR_CHECK(ret,"pthread_cond_signal");
	printf("send signal ok\n");
	pthread_join(pthId,NULL);
	printf("I am main thread\n");
	return 0;
}

1556802916953

代码解释:主线程创建结构体,包含mutex和cond,初始化后创建子线程,子线程上锁,并执行pthread_cond_wait阻塞等待条件变量的到来,sleep(1)后主线程发送signal信号使条件变量成立,故打印出 send signal ok和I am child,after wait,最后用join回收子线程。

pthread_cond_timedwait函数

功能:计时等待一个条件变量。线程解开mutex 指向的锁并被条件变量cond 阻塞。其中计时等待方式表示经历abstime 段时间后,即使条件变量不满足,阻塞也被解除

函数原型:

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

参3: 参看man sem_timedwait函数,查看struct timespec结构体。

​ struct timespec {

​ time_t tv_sec; /* seconds */ 秒

​ long tv_nsec; /* nanosecondes*/ 纳秒

​ }

形参abstime:绝对时间。

如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。

struct timespec t = {1, 0};

pthread_cond_timedwait (&cond, &mutex, &t); 只能定时到 1970年1月1日 00:00:01秒(早已经过去)

正确用法:

time_t cur = time(NULL); //获取当前时间。

struct timespec t;    //定义timespec 结构体变量t

t.tv_sec = cur+1; //定时1秒

pthread_cond_timedwait (&cond, &mutex, &t); //传参                           

struct timeval {

	time_t	tv_sec;  /* seconds * 秒/

	suseconds_t tv_usec;   /* microseconds * 微秒/

};

示例:

#include <func.h>
typedef struct{
	pthread_cond_t cond;
	pthread_mutex_t mutex;
}Data;
void* threadFunc(void* p)
{
	Data* pthreadInfo=(Data*)p;
	int ret;
	struct timespec t;
	t.tv_nsec=0;
	t.tv_sec=time(NULL)+5;
	pthread_mutex_lock(&pthreadInfo->mutex);
	ret=pthread_cond_timedwait(&pthreadInfo->cond,&pthreadInfo->mutex,&t);
	printf("pthread_cond_timedwait ret=%d\n",ret);
	pthread_mutex_unlock(&pthreadInfo->mutex);
	printf("I am child,after wait\n");
	pthread_exit(NULL);
}
int main()
{
	Data threadInfo;
	int ret;
	ret=pthread_cond_init(&threadInfo.cond,NULL);
	THREAD_ERROR_CHECK(ret,"pthread_cond_init");
	pthread_mutex_init(&threadInfo.mutex,NULL);
	pthread_t pthId;
	pthread_create(&pthId,NULL,threadFunc,&threadInfo);
	sleep(10);
	ret=pthread_cond_signal(&threadInfo.cond);
	THREAD_ERROR_CHECK(ret,"pthread_cond_signal");
	printf("send signal ok\n");
	pthread_join(pthId,NULL);
	printf("I am main thread\n");
	return 0;
}

1556803921394

代码解释:主线程控制10s后发signal但子线程设置5秒计时,超时则自动解锁,故程序启动后过了5秒后打印I am child,after wait,再过5秒后打印剩余部分!

pthread_cond_signal函数

唤醒至少一个阻塞在条件变量上的线程

int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_broadcast函数

唤醒全部阻塞在条件变量上的线程

​ int pthread_cond_broadcast(pthread_cond_t *cond);

条件变量的优点

​ 相较于mutex而言,条件变量可以减少竞争。

​ 如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如

果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成

生产,才会引起消费者之间的竞争。提高了程序效率。

生产者消费者模型

1556802243567

通过设置条件变量,在售票为空后自动补票。

#include <func.h>

typedef struct{
	int tickets;
	pthread_mutex_t mutex;
	pthread_cond_t cond;
}Train;
void* saleWindows1(void* p)
{
	Train *pSale=(Train*)p;
	int count=0;
	while(1)
	{
		pthread_mutex_lock(&pSale->mutex);
		if(pSale->tickets>0)
		{
			printf("I am saleWindows1,the tickets is %d\n",pSale->tickets);
			pSale->tickets--;
			if(0==pSale->tickets)
			{
				pthread_cond_signal(&pSale->cond);
			}
			count++;
			printf("sale finish,I am saleWindows1,the tickets is %d\n",pSale->tickets);
			pthread_mutex_unlock(&pSale->mutex);
		}else{
			pthread_mutex_unlock(&pSale->mutex);
			printf("I am windows1 sale %d\n",count);
			break;
		}
		sleep(1);
	}
	return NULL;
}
void* saleWindows2(void* p)
{
	Train *pSale=(Train*)p;
	int count=0;
	while(1)
	{
		pthread_mutex_lock(&pSale->mutex);
		if(pSale->tickets>0)
		{
			printf("I am saleWindows2,the tickets is %d\n",pSale->tickets);
			pSale->tickets--;
			if(0==pSale->tickets)
			{
				pthread_cond_signal(&pSale->cond);
			}
			count++;
			printf("sale finish,I am saleWindows2,the tickets is %d\n",pSale->tickets);
			pthread_mutex_unlock(&pSale->mutex);
		}else{
			pthread_mutex_unlock(&pSale->mutex);
			printf("I am windows2 sale %d\n",count);
			break;
		}
		sleep(1);
	}
	return NULL;
}
void* setTickets(void* p)
{
	Train *pSale=(Train*)p;
	pthread_mutex_lock(&pSale->mutex);
	if(pSale->tickets>0)
	{
		pthread_cond_wait(&pSale->cond,&pSale->mutex);
	}
	pSale->tickets=20;
	pthread_mutex_unlock(&pSale->mutex);
	return NULL;
}
typedef void* (*threadFunc)(void*);
#define N 3
int main()
{
	Train t;
	pthread_t pthId[N];
	int i;
	t.tickets=20;
	threadFunc pthreadFunc[N]={saleWindows1,saleWindows2,setTickets};
	pthread_mutex_init(&t.mutex,NULL);
	pthread_cond_init(&t.cond,NULL);
	for(i=0;i<N;i++)
	{
		pthread_create(pthId+i,NULL,pthreadFunc[i],&t);
	}
	for(i=0;i<N;i++)
	{
		pthread_join(pthId[i],NULL);
	}
	printf("sale over\n");
}

1556804039797

posted @ 2019-05-02 21:54  Mered1th  阅读(299)  评论(0编辑  收藏  举报