Linux操作系统学习第六篇博客

这篇博客详细介绍了Linux操作系统中的线程概念、特点、控制原语,包括pthread系列函数的使用,并探讨了线程的创建、回收、分离和取消。进一步,文章讲解了线程同步的重要性和多种实现方式,如互斥量、读写锁、条件变量和信号量,以及它们在进程间同步中的应用。

1、线程

(1)线程概念

    LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)
	
	进程:独立地址空间,拥有PCB
	
	线程:也有PCB,但没有独立的地址空间(共享)
	
	区别:在于是否共享地址空间。	独居(进程);合租(线程)。
	
	Linux下:	线程:最小的执行单位
		 		进程:最小分配资源单位,可看成是只有一个线程的进程。

在这里插入图片描述

(2)线程的特点

1|  轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone

2|  从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的

3|  进程可以蜕变成线程

4|  线程可看做寄存器和栈的集合

5|  在linux下,线程最是小的执行单位;进程是最小的分配资源单位

6|  察看LWP号(线程ID):ps –Lf pid 查看指定线程的lwp号。

(3)线程共享资源

1|  文件描述符表

2|  每种信号的处理方式

3|  当前工作目录

4|  用户ID和组ID

5|  内存地址空间 (.text/.data/.bss/heap/共享库)

(4)线程非共享资源

1|  线程id

2|  处理器现场和栈指针(内核栈)

3|  独立的栈空间(用户空间栈)

4|  errno变量

5|  信号屏蔽字

6|  调度优先级

(5)线程优、缺点

    优点:	1. 提高程序并发性	2. 开销小	3. 数据通信、共享数据方便
	缺点:	1. 库函数,不稳定	2. 调试、编写困难、gdb不支持	3. 对信号支持不好
	优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。

2、线程控制原语

(1)pthread_self函数

1|  作用:获取线程ID。其作用对应进程中 getpid() 函数。

2|  函数原型:pthread_t pthread_self(void);	

3|  返回值:返回值:成功:0;	失败:无!

4|  注意
    <1> 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现
	<2> 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)

(2)pthread_create函数

1|  函数作用:创建一个新线程。		其作用,对应进程中fork() 函数。

2|  函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

3|  返回值:成功:0;	失败:错误号	-----Linux环境下,所有线程特点,失败均直接返回错误号。

4|  参数
    <1> pthread_t:当前Linux中可理解为:typedef  unsigned long int  pthread_t;
    <2> 参数1:传出参数,保存系统为我们分配好的线程ID
	<3> 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
	<4> 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
	<5> 参数4:线程主函数执行期间所使用的参数。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thrd_func()
{
        printf("thread id is %lu,pid is %u\n",pthread_self(),getpid());
        return NULL;
}
int main(void)
{
        pthread_t tid;
        int ret;

        printf("In main1:thread id is %lu,pid is %u\n",pthread_self(),getpid());
        ret = pthread_create(&tid,NULL,thrd_func,NULL);
        if(ret!=0)
        {
                perror("pthread_creat error:\n");
                exit(1);
        }

        sleep(1);
        printf("In main2:thread id is %lu,pid is %u\n",pthread_self(),getpid());
        return 0;
 }                                                        

3、循环创建多个线程

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

void *thrd_func(void *arg)
{
		int i;
		i = (int)arg;
		sleep(i);
        printf("%dthread id is %lu,pid is %u\n",i+i,pthread_self(),getpid());
        return NULL;
}
int main(void)
{
        pthread_t tid;
        int ret,i;

        for(i = 0;i < 5;i++)
        {
				ret = pthread_create(&tid,NULL,thrd_func,(void *)i);
        		if(ret!=0)
       			{
                	perror("pthread_creat error:\n");
                	exit(1);
        		}
		}
       
        sleep(i);
        printf("In main2:thread id is %lu,pid is %u\n",pthread_self(),getpid());
        return 0;
 }                                                        

4、pthread_exit函数

1|  作用:将单个线程退出

2|  原型:void pthread_exit(void *retval);

3|  参数:retval表示线程退出状态,通常传NULL
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thrd_func(void *arg)
{
		int i;
		i = (int)arg;
		sleep(i);
        printf("I am %d thread,thread id is %lu,pid is %u\n",i+i,pthread_self(),getpid());
        return NULL;
}
int main(void)
{
        pthread_t tid;
        int ret,i;

        for(i = 0;i < 5;i++)
        {
				ret = pthread_create(&tid,NULL,thrd_func,(void *)i);
        		if(ret!=0)
       			{
                	perror("pthread_creat error:\n");
                	exit(1);
        		}
		}
       
        sleep(i);
        printf("In main2:thread id is %lu,pid is %u\n",pthread_self(),getpid());
        
        pthread_exit(NULL);
 }                                                        

5、pthread_join函数回收子进程

1|  作用:回收子线程(其作用,对应进程中 waitpid() 函数。)

2|  函数原型:int pthread_join(pthread_t thread, void **retval); 

3|  返回值:成功:0;失败:错误号

4|  参数:thread:线程ID (【注意】:不是指针);retval:存储线程结束状态。
	对比记忆:
		进程中:main返回值、exit参数-->int;等待子进程结束 wait 函数参数-->int *
		线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *thrd_func(void *arg)
{
		pthread_exit((void*)10);
}
int main(void)
{
        pthread_t tid;
        int ret;
        int *retval;
        
        printf("In main 1:thread id = %lu,pid = %u\n",pthread_self(),getpid());
        
		ret = pthread_create(&tid,NULL,thrd_func,NULL);
        if(ret!=0)
       	{
              perror("pthread_creat error:\n");
             //fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
              exit(1);
        }
   		
        pthread_join(tid,(void **)&retval);
        printf("-------------%d\n",(int)retval);
        pthread_exit(NULL);
 }                                              
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

typedef struct{
	char ch;
	int var;
	char str[64];
}exit_t;
void *thrd_func(void *arg)
{
		exit_t *retvar = (exit_t *)malloc(sizeof(exit_t));
		//结构体赋值
		retvar->ch = 'm';
		retvar->var = 200;
		strcpy(retvar->str,"my thread\n");
		
		pthread_exit((void*)10);
}
int main(void)
{
        pthread_t tid;
        int ret;
        exit_t *retval;
        
        printf("In main 1:thread id = %lu,pid = %u\n",pthread_self(),getpid());
        
		ret = pthread_create(&tid,NULL,thrd_func,(void *)i);
        if(ret!=0)
       	{
              perror("pthread_creat error:\n");
             //fprintf(stderr,"pthread_create error:%s\n",strerror(ret));
              exit(1);
        }
   		
        pthread_join(tid,(void **)&retval);
        printf("ch = %c,var = %d,str = %s\n",retval->ch,retval->var,retval->str);
        pthread_exit(NULL);
 }                                              

6、循环创建的多个子线程回收

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

imt var = 100;

void *tfn(void *arg)
{
	int i;
	i = (int)arg;
	
	sleep(i);
	if(i == 1)
	{
		var = 333;
		printf("I'm %dth pthread,pthread_id = %lu,var = %d\n",i+1,pthread_self(),var);
		return (void *)var;
	}
	else if(i == 3)
	{
		var = 777;
		printf("I'm %dth pthread,pthread_id = %lu,var = %d\n",i+1,pthread_self(),var);
		pthread_exit((void *)var);	
	}
	else
	{
		printf("I'm %dth pthread,pthread_id = %lu,var = %d\n",i+1,pthread_self(),var);
		pthread_exit((void *)var);
	}
}
int main()
{
	pthread_t tid[5];
	int i;
	int *ret[5];
	
	for(i=0;i<5;i++)
	{
		pthread_create(&tid,NULL,tfn,(void *)i);
	}
	for(i=0;i<5;i++)
	{
		pthread_join(tid[i],(void **)&ret[i]);
		printf("==========%d 's ret = %d\n",i,(int)ret[i]);	
	}
	return 0;	
} 

7、线程分离

(1)pthread_detach函数

1|  作用:实现线程分离

2|  函数原型:int pthread_detach(pthread_t thread);	

3|  返回值:   成功:0;失败:错误号

8、杀死(取消)线程

(1)pthread_cancel函数

1|  作用:杀死(取消)线程,对应进程中 kill() 函数。

2|  函数原型:int pthread_cancel(pthread_t thread);	

3|  返回值:  成功:0;失败:错误号
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

void *tfn1(void *arg)
{
	printf("thread 1 retruning\n");
	return (void *)111;
}

void *tfn2(void *arg)
{
	printf("thread 2 retruning\n");
	return (void *)222;
}

void *tfn3(void *arg)
{
	while(1)
	{
		pthread_testcancel();
	}
}

int main()
{
	pthread_t tid;
	void *tret = NULL;
	
	pthread_create(&tid,NULL,tfn1,NULL);
	pthread_join(tid,&tret);
	printf("thread 1 exit code = %d\n\n",(int)tret);
	
	pthread_create(&tid,NULL,tfn2,NULL);
	pthread_join(tid,&tret);
	printf("thread 2 exit code = %d\n\n",(int)tret);

	pthread_create(&tid,NULL,tfn3,NULL);
	sleep(3);
	pthread_cancel(tid);
	pthread_join(tid,&tret);
	printf("thread 3 exit code = %d\n\n",(int)tret);
	
	return 0;
}

(2)控制原语对比

	进程			线程
	fork			pthread_create
	exit			pthread_exit
	wait			pthread_join
	kill			pthread_cancel
	getpid			pthread_self		命名空间

9、线程属性

(1)线程属性初始化

1|  注意:应先初始化线程属性,再pthread_create创建线程

2|  初始化线程属性
    int pthread_attr_init(pthread_attr_t *attr); 成功:0;失败:错误号

3|  销毁线程属性所占用的资源
    int pthread_attr_destroy(pthread_attr_t *attr); 成功:0;失败:错误号

(2)线程的分离状态

1|  设置线程属性,分离or非分离
    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 

2|  获取程属性,分离or非分离
 	int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); 
	
3|  参数:	
	attr:已初始化的线程属性
	detachstate:	PTHREAD_CREATE_DETACHED(分离线程)、PTHREAD _CREATE_JOINABLE(非分离线程)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void *thrd_func(void *arg)
{
	pthread_exit((void *)77);
}

int main()
{
	pthread_t tid;
	int ret;
	pthread_attr_t attr;

	ret = pthread_attr_init(&attr);
	if(ret!=0)
	{
		fprintf(stderr,"pthread_init error:%s\n",strerror(ret));
		exit(1);
	}
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	ret = pthread_create(&tid,&attr,thrd_func,NULL);
	if(ret!=0)
	{
		fprintf(stderr,"pthread_init error:%s\n",strerror(ret));
		exit(1);
	}
	ret = pthread_join(tid,NULL);
	{
		fprintf(stderr,"pthread_init error:%s\n",strerror(ret));
		exit(1);
	}
	printf("--------------------join ret = %d\n",ret);
	
	pthread_exit((void *)1);
}

(3)线程的栈地址

1|  当进程栈地址空间不够用时,指定新建线程使用由malloc分配的空间作为自己的栈空间。通过pthread_attr_setstack和pthread_attr_getstack两个函数分别设置和获取线程的栈地址。

2|  int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize); 成功:0;失败:错误号

3|  int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize); 成功:0;失败:错误号

4|  参数:	attr:指向一个线程属性的指针
			stackaddr:返回获取的栈地址
			stacksize:返回获取的栈大小

(4)线程的栈大小

1|  当系统中有很多线程时,可能需要减小每个线程栈的默认大小,防止进程的地址空间不够用,当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增大线程栈的默认大小。

2|  函数pthread_attr_getstacksize和 pthread_attr_setstacksize提供设置。

3|  int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 成功:0;失败:错误号

4|  int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize); 成功:0;失败:错误号

5|  参数:	attr:指向一个线程属性的指针
			stacksize:返回线程的堆栈大小
#include <pthread.h>

#define SIZE 0x100000
void *th_fun(void *arg)
{
	while (1) 
		sleep(1);
}
int main(void)
{
	pthread_t tid;
	int err, detachstate, i = 1;
	pthread_attr_t attr;
	size_t stacksize;
	void *stackaddr;

	pthread_attr_init(&attr);		
	pthread_attr_getstack(&attr, &stackaddr, &stacksize);
	pthread_attr_getdetachstate(&attr, &detachstate);

	if (detachstate == PTHREAD_CREATE_DETACHED)
		printf("thread detached\n");
	else if (detachstate == PTHREAD_CREATE_JOINABLE)
		printf("thread join\n");
	else
		printf("thread unknown\n");

	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	while (1) {
		stackaddr = malloc(SIZE);
		if (stackaddr == NULL) {
			perror("malloc");
			exit(1);
		}
		stacksize = SIZE;
		pthread_attr_setstack(&attr, stackaddr, stacksize);
		err = pthread_create(&tid, &attr, th_fun, NULL);
		if (err != 0) {
			printf("%s\n", strerror(err));
			exit(1);
		}
		printf("%d\n", i++);
	}
	pthread_attr_destroy(&attr);
	return 0;
}										

10、线程使用注意事项

1|  主线程退出其他线程不退出,主线程应调用pthread_exit

2|  避免僵尸线程
    pthread_join
   pthread_detach
   pthread_create指定分离属性
   被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;

3|  malloc和mmap申请的内存可以被其他线程释放 

4|  应避免在多线程模型中调用fork除非,马上exec,子进程中只有调用fork的线程存在,其他线程在子进程中均pthread_exit

5|  信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制

11、线程同步

(1)概念

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

(2)注意点

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

12、互斥量mutex

(1)概念

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

2|  每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
	
3|  资源还是共享的,线程间也还是竞争的,							
	
4|  但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

(2)主要应用函数

1|  pthread_mutex_init函数

2|	pthread_mutex_destroy函数

3|	pthread_mutex_lock函数

4|	pthread_mutex_trylock函数

5|	pthread_mutex_unlock函数

6|  以上5个函数的返回值都是:成功返回0, 失败返回错误号。	
	pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。
	pthread_mutex_t mutex; 变量mutex只有两种取值1、0。

13、函数详述

(1)pthread_mutex_init函数

1|  作用:初始化一个互斥锁(互斥量) ---> 初值可看作1
	
2|  函数原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
	
3|  参数说明:
	<1> 参1:传出参数,调用时应传 &mutex	
	<2> 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性
        静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g.  pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
        动态初始化:局部变量应采用动态初始化。e.g.  pthread_mutex_init(&mutex, NULL)

(2)pthread_mutex_destroy函数

1|  作用:销毁一个互斥锁
	
2|  函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);

(3)pthread_mutex_lock函数

1|  作用:加锁。可理解为将mutex--(或-1)
	
2|  函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);

(4)pthread_mutex_unlock函数

1|  作用:解锁。可理解为将mutex ++(或+1)
	
2|  函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);

(5)pthread_mutex_trylock函数

1|  作用:尝试加锁
	
2|  函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);

14、读写锁

(1)介绍

1|  读锁、写锁并行阻塞,写锁优先级高
	
2|  读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
	
3|  读写锁非常适合于对数据结构读的次数远大于写的情况。

(2)相关函数

	pthread_rwlock_init函数
	pthread_rwlock_destroy函数
	pthread_rwlock_rdlock函数  
	pthread_rwlock_wrlock函数
	pthread_rwlock_tryrdlock函数
	pthread_rwlock_trywrlock函数
	pthread_rwlock_unlock函数
	以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。	
	pthread_rwlock_t类型	用于定义一个读写锁变量。
	pthread_rwlock_t rwlock;

(3)函数详述

1|pthread_rwlock_init函数
    功能: 	初始化一把读写锁
	函数原型:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
	参2:attr表读写锁属性,通常使用默认属性,传NULL即可。

2|  pthread_rwlock_destroy函数
	功能:	销毁一把读写锁
	函数原型:int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

3|  pthread_rwlock_rdlock函数
	功能:	以读方式请求读写锁。(常简称为:请求读锁)
    函数原型:int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

4|  pthread_rwlock_wrlock函数
	功能:	以写方式请求读写锁。(常简称为:请求写锁)
    函数原型:int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

5|  pthread_rwlock_unlock函数
	功能:	解锁
	函数原型:int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

6|  pthread_rwlock_tryrdlock函数
	功能:	非阻塞以读方式请求读写锁(非阻塞请求读锁)
	函数原型:int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

7|  pthread_rwlock_trywrlock函数
	功能:	非阻塞以写方式请求读写锁(非阻塞请求写锁)
	函数原型:int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int counter;
pthread_rwlock_t rwlock;

/* 3个线程不定时写同一全局资源,5个线程不定时读同一全局资源 */
void *th_write(void *arg)
{
    int t, i = (int)arg;
    while (1) {
        pthread_rwlock_wrlock(&rwlock);
        t = counter;
        usleep(1000);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);
        usleep(10000);
    }
    return NULL;
}
void *th_read(void *arg)
{
    int i = (int)arg;

    while (1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);
        usleep(2000);
    }
    return NULL;
}
int main(void)
{
    int i;
    pthread_t tid[8];
    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);
    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);
    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);

    pthread_rwlock_destroy(&rwlock);
    return 0;
}													

15、条件变量

(1)概述

    条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。

(2)相关函数

	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;

(3)函数详述

1|  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;

2|  pthread_cond_destroy函数
	功能:	销毁一个条件变量
	函数原型:int pthread_cond_destroy(pthread_cond_t *cond);

3|  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.两步为一个原子操作。
	<3> 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

4|  pthread_cond_signal函数
	功能:	唤醒至少一个阻塞在条件变量上的线程
 	函数原型:int pthread_cond_signal(pthread_cond_t *cond);

5|  pthread_cond_broadcast函数
	功能:	唤醒全部阻塞在条件变量上的线程
    函数原型:int pthread_cond_broadcast(pthread_cond_t *cond);
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct msg {
    struct msg *next;
    int num;
};
struct msg *head;

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    struct msg *mp;
    for (;;) {
        pthread_mutex_lock(&lock);
        while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗
            pthread_cond_wait(&has_product, &lock);
        }
        mp = head;      
        head = mp->next;    			//模拟消费掉一个产品
        pthread_mutex_unlock(&lock);

        printf("-Consume ---%d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }
}
void *producer(void *p)
{
    struct msg *mp;
    while (1) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);

        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }
}
int main(int argc, char *argv[])
{
    pthread_t pid, cid;
    srand(time(NULL));

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}						

(4)条件变量的优点

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

2|  如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

(5)生产者消费者条件变量模型

#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

struct msg {
    struct msg *next;
    int num;
};
struct msg *head;

pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    struct msg *mp;
    for (;;) {
        pthread_mutex_lock(&lock);
        while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗
            pthread_cond_wait(&has_product, &lock);
        }
        mp = head;      
        head = mp->next;    			//模拟消费掉一个产品
        pthread_mutex_unlock(&lock);

        printf("-Consume ---%d\n", mp->num);
        free(mp);
        sleep(rand() % 5);
    }
}
void *producer(void *p)
{
    struct msg *mp;
    while (1) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);

        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }
}
int main(int argc, char *argv[])
{
    pthread_t pid, cid;
    srand(time(NULL));

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);
    return 0;
}										

16、信号量(进化版的互斥锁(1 --> N))

(1)主要应用函数

	sem_init函数
	sem_destroy函数
	sem_wait函数
	sem_trywait函数	
	sem_timedwait函数	
	sem_post函数

1|  以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。(注意,它们没有pthread前缀)
	
2|  sem_t类型,本质仍是结构体。但应用期间可简单看作为整数,忽略实现细节(类似于使用文件描述符)。 
	
3|  sem_t sem; 规定信号量sem不能 < 0。头文件 <semaphore.h>

(2)信号量基本操作

sem_wait:	    1. 信号量大于0,则信号量--		(类比pthread_mutex_lock)
	  |			2. 信号量等于0,造成线程阻塞
	对应
	  |
sem_post:	将信号量++,同时唤醒阻塞在信号量上的线程	(类比pthread_mutex_unlock)

但,由于sem_t的实现对用户隐藏,所以所谓的++、--操作只能通过函数来实现,而不能直接++、--符号。
信号量的初值,决定了占用信号量的线程的个数。
1|  sem_init函数
	作用:	初始化一个信号量
	函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
	参1:sem信号量	
	参2:pshared取0用于线程间;取非0(一般为1)用于进程间	
	参3:value指定信号量初值

2|  sem_destroy函数
	作用:	销毁一个信号量
	函数原型:int sem_destroy(sem_t *sem);

3|  sem_wait函数
	作用:	给信号量加锁 -- 
	函数原型:int sem_wait(sem_t *sem);

4|	sem_post函数
	作用:	给信号量解锁 ++
	 函数原型:int sem_post(sem_t *sem);	

5|	sem_trywait函数
	作用:	尝试对信号量加锁 --	(与sem_wait的区别类比lock和trylock)
	 函数原型:int sem_trywait(sem_t *sem);	

6|	sem_timedwait函数
	作用:	限时尝试对信号量加锁 --
	函数原型:int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
	参2:abs_timeout采用的是绝对时间。			
	定时1秒:
		time_t cur = time(NULL); 获取当前时间。
		struct timespec t;	定义timespec 结构体变量t
		t.tv_sec = cur+1; 定时1秒
		t.tv_nsec = t.tv_sec +100; 
		sem_timedwait(&sem, &t); 传参

17、进程间同步

(1)互斥量mutex

	进程间也可以使用互斥锁,来达到同步的目的。但应在pthread_mutex_init初始化之前,修改其属性为进程间共享。mutex的属性修改函数主要有以下几个。

(2)主要应用函数

1|  pthread_mutexattr_t mattr 类型:		用于定义mutex锁的【属性】
	
2|  pthread_mutexattr_init函数:			初始化一个mutex属性对象
		int pthread_mutexattr_init(pthread_mutexattr_t *attr);
	
3|  pthread_mutexattr_destroy函数:		销毁mutex属性对象 (而非销毁锁)
		int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
	
4|  pthread_mutexattr_setpshared函数:	修改mutex属性。
		int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
		参2:pshared取值:
						<1>	线程锁:PTHREAD_PROCESS_PRIVATE (mutex的默认属性即为线程锁,进程间私有)
						<2> 进程锁:PTHREAD_PROCESS_SHARED
#include <fcntl.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/wait.h>

struct mt {
    int num;
    pthread_mutex_t mutex;
    pthread_mutexattr_t mutexattr;
};

int main(void)
{
    int fd, i;
    struct mt *mm;
    pid_t pid;

    fd = open("mt_test", O_CREAT | O_RDWR, 0777);
    ftruncate(fd, sizeof(*mm));
    mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);
    unlink("mt_test");
    //mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
    memset(mm, 0, sizeof(*mm));

    pthread_mutexattr_init(&mm->mutexattr);                                  //初始化mutex属性对象
    pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED);    //修改属性为进程间共享
    pthread_mutex_init(&mm->mutex, &mm->mutexattr);                          //初始化一把mutex琐

    pid = fork();
    if (pid == 0) {
        for (i = 0; i < 10; i++) {
            pthread_mutex_lock(&mm->mutex);
            (mm->num)++;
            printf("-child----num++   %d\n", mm->num);
            pthread_mutex_unlock(&mm->mutex);
            sleep(1);
        }
    } else if (pid > 0) {
        for ( i = 0; i < 10; i++) {
            sleep(1);
            pthread_mutex_lock(&mm->mutex);
            mm->num += 2;
            printf("-parent---num+=2  %d\n", mm->num);
            pthread_mutex_unlock(&mm->mutex);
        }
        wait(NULL);
    }

    pthread_mutexattr_destroy(&mm->mutexattr);          //销毁mutex属性对象
    pthread_mutex_destroy(&mm->mutex);                //销毁mutex
    munmap(mm,sizeof(*mm));                          //释放映射区
    return 0;
}								

(3)文件锁

1|  fcntl函数
	<1> 作用:获取、设置文件访问控制属性。
	<2> 函数原型:int fcntl(int fd, int cmd, ... /* arg */ );
	<3> 参数说明:
		
		参2:
		F_SETLK (struct flock *)	设置文件锁(trylock)
		F_SETLKW (struct flock *) 设置文件锁(lock)W --> wait
		F_GETLK (struct flock *)	获取文件锁
		
		参3:
        struct flock {
              ...
              short l_type;    	锁的类型:F_RDLCK 、F_WRLCK 、F_UNLCK
              short l_whence;  	偏移位置:SEEK_SET、SEEK_CUR、SEEK_END 
              off_t l_start;   		起始偏移:1000
              off_t l_len;     		长度:0表示整个文件加锁
              pid_t l_pid;     	持有该锁的进程ID:(F_GETLK only)
              ...
         };
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

void sys_err(char *str)
{
    perror(str); exit(1);
}
int main(int argc, char *argv[])
{
    int fd;
    struct flock f_lock;

    if (argc < 2) {
        printf("./a.out filename\n"); exit(1);
    }
    if ((fd = open(argv[1], O_RDWR)) < 0)
        sys_err("open");

    //f_lock.l_type = F_WRLCK;        /*选用写琐*/
    f_lock.l_type = F_RDLCK;          /*选用读琐*/ 

    f_lock.l_whence = SEEK_SET;
    f_lock.l_start = 0;
    f_lock.l_len = 0;               /* 0表示整个文件加锁 */

    fcntl(fd, F_SETLKW, &f_lock);
    printf("get flock\n");
    sleep(10);

    f_lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLKW, &f_lock);
    printf("un flock\n");

    close(fd);	 return 0;
}													

(4)注意

		多线程间共享文件描述符,而给文件加锁,是通过修改文件描述符所指向的文件结构体中的成员变量来实现的。因此,多线程中无法使用文件锁。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值