• 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
    ");
    	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
    ",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
    ",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
    ");
    	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
    ");
    	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
    ",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
    ",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
    ");
    }
    //线程加锁以后被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
    ",pSale->tickets);
    			pSale->tickets--;
    			count++;
    			//printf("sale finish,I am saleWindows1,the tickets is %d
    ",pSale->tickets);
    			pthread_mutex_unlock(&pSale->mutex);
    		}else{
    			pthread_mutex_unlock(&pSale->mutex);
    			printf("I am windows1 sale %d
    ",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
    ",pSale->tickets);
    			pSale->tickets--;
    			count++;
    			//printf("sale finish,I am saleWindows2,the tickets is %d
    ",pSale->tickets);
    			pthread_mutex_unlock(&pSale->mutex);
    		}else{
    			pthread_mutex_unlock(&pSale->mutex);
    			printf("I am windows2 sale %d
    ",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
    ");
    }
    

    条件变量

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

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

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

    ​ 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
    ");
    	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
    ");
    	pthread_join(pthId,NULL);
    	printf("I am main thread
    ");
    	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
    ",ret);
    	pthread_mutex_unlock(&pthreadInfo->mutex);
    	printf("I am child,after wait
    ");
    	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
    ");
    	pthread_join(pthId,NULL);
    	printf("I am main thread
    ");
    	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
    ",pSale->tickets);
    			pSale->tickets--;
    			if(0==pSale->tickets)
    			{
    				pthread_cond_signal(&pSale->cond);
    			}
    			count++;
    			printf("sale finish,I am saleWindows1,the tickets is %d
    ",pSale->tickets);
    			pthread_mutex_unlock(&pSale->mutex);
    		}else{
    			pthread_mutex_unlock(&pSale->mutex);
    			printf("I am windows1 sale %d
    ",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
    ",pSale->tickets);
    			pSale->tickets--;
    			if(0==pSale->tickets)
    			{
    				pthread_cond_signal(&pSale->cond);
    			}
    			count++;
    			printf("sale finish,I am saleWindows2,the tickets is %d
    ",pSale->tickets);
    			pthread_mutex_unlock(&pSale->mutex);
    		}else{
    			pthread_mutex_unlock(&pSale->mutex);
    			printf("I am windows2 sale %d
    ",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
    ");
    }
    

    1556804039797

  • 相关阅读:
    vue-实践1
    node 基本使用
    vue通信
    初始AMD和Common.js
    vue正确引入第三方包
    常见的java设计模式
    springboot加ES实现全局检索
    Cookie丢失的原因
    动态SQL
    用Java实现给图片添加文字
  • 原文地址:https://www.cnblogs.com/Mered1th/p/10803533.html
Copyright © 2020-2023  润新知