Linux高性能服务器编程-多线程

目录

线程的创建与退出

线程属性

将线程设置为分离属性

线程同步

互斥锁    

死锁

读写锁

条件变量 

信号量

多线程环境

可重入函数


线程就是轻量级进程,线程是最小的执行单位,进程是最小的资源分配单位。

线程的创建与退出
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                void *(*start_routine) (void *), void *arg);、
/*
函数参数:
   	pthread_t:传出参数,保存系统为我们分配好的线程ID
	当前Linux中可理解为:typedef unsigned long int pthread_t。
	attr:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
	start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
	arg:线程主函数执行期间所使用的参数。
返回值
	成功,返回0
	失败,返回错误号
*/

void pthread_exit(void *retval);	
//函数参数
//	retval表示线程退出状态,通常传NULL

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
const int n = 5;
void *mythread(void *arg)
{
    int i = *(int *) arg;
    printf("the %d-th sub-thread, p_id = %ld\n",i,pthread_self());
    pthread_exit(); 
}
int main()
{
    pthread_t thread[n];
    int arr[n]];
    int ret = 0;
    for(int i = 0; i < n)
    {
        arr[i] = i;
        ret = pthread_create(&thread[i],NULL,mythread,&arr[i]);
        if(ret != 0)
        {
            printf("pthread_create error\n");
            return -1;
        }
    }
    printf("main thread pid = %d  id == %ld\n",getpid(),pthread_self());
    sleep(10);
    return 0;
}
线程属性

        linux下线程的属性是可以根据实际项目需要,进行设置,线程创建时采用线程的默认属性,可以根据需要设置线程的分离属性。

线程的分离状态决定一个线程以什么样的方式来终止自己,有两种状态:

        非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。

        分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。

将线程设置为分离属性
//第1步:定义线程属性类型类型的变量
pthread_attr_t  attr;	
//第2步:对线程属性变量进行初始化
int pthread_attr_init (pthread_attr_t* attr);
//第3步:设置线程为分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
/*
参数:
	attr: 线程属性
	detachstate:
	PTHREAD_CREATE_DETACHED(分离)
	PTHREAD_CREATE_JOINABLE(非分离)
*/
pthread_create(&thread, &attr, mythread, NULL);

     也可以用pthread_detach函数)来设置线程分离,pthread_detach函数是在创建线程之后调用的。

int pthread_detach(pthread_t thread);	
//获取线程的退出状态  默认
int pthread_join(pthread_t thread, void **retval); 

        不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。

线程同步

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

互斥锁    
pthread_mutex_t mutex; //变量mutex只有两种取值1、0。
int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
						        const pthread_mutexattr_t *restrict attr);
/*
函数描述:
    初始化一个互斥锁(互斥量) ---> 初值可看作1
函数参数
	mutex:传出参数,调用时应传 &mutex	
	attr:互斥锁属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。
    restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,、                                    
                   只能通过本指针完成。不能通过除本指针以外的其他变量或
                   指针修改互斥量mutex的两种初始化方式:
	静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),
                可以直接使用宏进行初始化。
                pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
	动态初始化:局部变量应采用动态初始化。pthread_mutex_init(&mutex, NULL)
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁一个互斥锁
int pthread_mutex_lock(pthread_mutex_t *mutex); //对互斥所加锁,可理解为将mutex--
int pthread_mutex_unlock(pthread_mutex_t *mutex); //对互斥所解锁,可理解为将mutex ++

         lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。

        unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。

        创建两个线程,让两个线程共享一个全局变量int number, 然后让每个线程数5000次数,可能出现某个线程少加的情况。这是因为线程操作共享资源的先后顺序不确定,可以出现覆盖值得现象,利用互斥锁来保证共享资源得同步。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
//定义一把锁
pthread_mutex_t mutex;
int num = 0;
void *fun_thread1(void *arg)
{
    int n;
    for(int i = 0; i < 5000; i++)
    {
        pthread_mutex_lock(&mutex);
        n = num;
        n++;
        num = n;
        pthread_mutex_unlock(&mutex);
    }
}
void *fun_thread2(void *arg)
{
    int n;
    for(int i = 0; i < 5000; i++)
    {
        pthread_mutex_lock(&mutex);
        n = num;
        n++;
        num = n;
        pthread_mutex_unlock(&mutex);
    }
}
int main()
{
    int ret = 0;
    pthread_mutex_init(&mutex,NULL);
    pthread_t thread1;
    pthread_t thread2;
    ret = pthread_create(&thread1,NULL,fun_thread1,NULL);
    if(ret != 0)
    {
        return -1;
    }
    ret = pthread_create(&thread2,NULL,fun_thread2,NULL);
    if(ret != 0)
    {
        return -1;
    }
    pthread_join(thread1,NULL); //非分离状态
    pthread_join(thread2,NULL);
    printf("num = %d\n",num);
    pthread_mutex_destroy(&mutex); //释放锁
    return 0;
}

           使用互斥锁之后,两个线程由并行变为了串行,效率降低了,但是可以使两个线程同步操作共享资源,从而解决了数据不一致的问题。 

死锁

        死锁使用互斥锁不当引起的一种现象。当一个线程自己锁自己,即调用两次互斥锁的上锁过程会出现死锁。此外,当线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁。

                        

解决死锁:

        让线程按照一定的顺序去访问共享资源

        在访问其他锁的时候,需要先将自己的锁解开

        调用pthread_mutex_trylock,如果加锁不成功会立刻返回

读写锁

        读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。适用于读次数远大于写的情况。

      对写加锁时,解锁前,所有对该锁加锁的线程都会被阻塞。 对读加锁时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。

pthread_rwlock_t rwlock; //定义
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //初始化
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁读写锁  
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); //加读锁           
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//尝试加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//加写锁
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//尝试加写锁
int pthread_rwlock_unlock(&pthread_rwlock_t *rwlock);//解锁

        读并行,写独占,当读写同时等待锁的时候写的优先级高。

3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。

#include <stdio.h>
#include <pthread.h>
//定义一把读写锁
pthread_rwlock_t rwlock;
const int n = 8;
int number = 0;
//线程回调函数--写
void *write_thread(void *arg)
{
    int i = *(int *) arg;
    int cur = 0;
    while(1)
    {
        //写线程加写锁,其他线程不能写
        pthread_rwlock_wrlock(&rwlock);
        cur = number;
        cur ++;
        number = cur;
        printf("the %d-th thread write number = %d\n",i,number);
        //解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(rand()%5);
    }
}

void *read_thread(void *arg)
{
     int i = *(int *) arg;
    int cur = 0;
    while(1)
    {
        //读线程加读锁,
        pthread_rwlock_rdlock(&rwlock);
        cur = number;
        printf("the %d-th thread read number = %d\n",i,cur);
        //解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(rand()%5);
    }
}

int main()
{
    int ret = 0;
    pthread_rwlock_init(&rwlock,NULL);
    pthread_t thread[n];
    int arr[n];
    //创建3个写线程
    for(int i = 0; i < 3; i++)
    {
        arr[i] = i;
        ret = pthread_create(&thread[i],NULL,write_thread,&arr[i]);
        if(ret != 0)
        {
            return -1;
        }
    }
    //创建5个读线程
    for(int i = 3; i < n; i++)
    {
        arr[i] = i;
        ret = pthread_create(&thread[i],NULL,read_thread,&arr[i]);
        if(ret != 0)
        {
            return -1;
        }
    }
    for(int i = 0; i < n; i++)
    {
        pthread_join(thread[i],NULL);
    }
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

结果如下: 

                                  

条件变量 

        条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。使用互斥量保护共享数据;使用条件变量可以使线程阻塞,等待某个条件的发生, 当条件满足的时候解除阻塞。

        条件变量两个动作:条件满足---解除阻塞,不满足----阻塞。

pthread_cond_t  cond; //定义
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*
函数描述: 
    条件不满足, 引起线程阻塞并解锁;
    条件满足, 解除线程阻塞, 并加锁。
函数参数:
    cond: 条件变量
    mutex: 互斥锁变量
*/
int pthread_cond_signal(pthread_cond_t *cond);//唤醒至少一个阻塞在该条件变量上的线程

 生产者消费者模型

#include <stdio.h>
#include <pthread.h>
typedef struct node
{
   int val;
   struct node * next;
}Node;
Node *head = nullptr;
//定义一把锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//生产者线程
void *producer(void *arg)
{
    Node *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//生产一个节点
		pNode = (Node *)malloc(sizeof(Node));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->val = rand()%1000;
		printf("the %d-th thread produce int %d\n", n, pNode->val);
		//加锁
		pthread_mutex_lock(&mutex);
		pNode->next = head;
		head = pNode;
		//解锁
		pthread_mutex_unlock(&mutex);
		//通知消费者线程解除阻塞
		pthread_cond_signal(&cond);
		sleep(rand()%3);
	}
}
//消费者线程
void *consumer(void *arg)
{
	Node *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		if(head==NULL)
		{
			//若条件不满足,需要阻塞等待
			//若条件不满足,则阻塞等待并解锁;
			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
			pthread_cond_wait(&cond, &mutex);
		}
		if(head==NULL)
		{
			//解锁
			pthread_mutex_unlock(&mutex);	
			continue;
		}
		printf("the %d-th constumer take int %d\n", n, head->val);	
		pNode = head;
		head = head->next;
		//解锁
		pthread_mutex_unlock(&mutex);
		free(pNode);
		pNode = NULL;
		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	int i = 0;
	pthread_t thread1[5];
	pthread_t thread2[5];
	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL);
	//条件变量初始化
	pthread_cond_init(&cond, NULL);
	int arr[5];
	for(i=0; i<5; i++)
	{
		arr[i]= i;
		//创建生产者线程
		ret = pthread_create(&thread1[i], NULL, producer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}
		//创建消费者线程
		ret = pthread_create(&thread2[i], NULL, consumer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}
	}
	//等待线程结束
	for(i=0; i<5; i++)
	{
		pthread_join(thread1[i], NULL);
		pthread_join(thread2[i], NULL);
	}
	//释放互斥锁
	pthread_mutex_destroy(&mutex);
	//释放条件变量
	pthread_cond_destroy(&cond);
	return 0;
}
信号量

        信号量相当于多把锁, 加强版的互斥锁。

sem_t sem; //定义
int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化
//pshared: 0表示线程同步, 1表示进程同步
//value: 最多有几个线程操作共享数据
int sem_wait(sem_t *sem);//调用该函数一次, 相当于sem--, 当sem为0的时候, 引起阻塞
int sem_post(sem_t *sem);//调用一次, 相当于sem++
int sem_trywait(sem_t *sem);//尝试加锁, 若失败直接返回, 不阻塞
int sem_destroy(sem_t *sem);//销毁信号量

信号量实现生产者消费者模型

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct node
{
	int data;
	struct node *next;
}Node;
Node *head = NULL;
//定义信号量
sem_t sem_producer;
sem_t sem_consumer;

//生产者线程
void *producer(void *arg)
{
	Node *pNode = NULL;
	while(1)
	{
		//生产一个节点
		pNode = (Node *)malloc(sizeof(Node));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P:[%d]\n", pNode->data);
		//加锁
		sem_wait(&sem_producer); //--
		pNode->next = head;
		head = pNode;
		//解锁
		sem_post(&sem_consumer);  //相当于++
		sleep(rand()%3);
	}
}
//消费者线程
void *consumer(void *arg)
{
	Node *pNode = NULL;
	while(1)
	{
		//加锁
		sem_wait(&sem_consumer); //相当于--
		printf("C:[%d]\n", head->data);	
		pNode = head;
		head = head->next;
		//解锁
		sem_post(&sem_producer); //相当于++
		free(pNode);
		pNode = NULL;
		sleep(rand()%3);
	}
}
int main()
{
	int ret;
	pthread_t thread1;
	pthread_t thread2;

	//初始化信号量
	sem_init(&sem_producer, 0, 5);
	sem_init(&sem_consumer, 0, 0);
	//创建生产者线程
	ret = pthread_create(&thread1, NULL, producer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}
	//创建消费者线程
	ret = pthread_create(&thread2, NULL, consumer, NULL);
	if(ret!=0)
	{
		printf("pthread_create error, [%s]\n", strerror(ret));
		return -1;
	}
	//等待线程结束
	pthread_join(thread1, NULL);
	pthread_join(thread2, NULL);
	//释放信号量资源
	sem_destroy(&sem_producer);
	sem_destroy(&sem_consumer);
	return 0;
}
多线程环境
可重入函数

        如果一个函数能够被多个线程同时调用且不发生竞态条件(不必担心数据是否会出错),则我们称它是可重入函数。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值