Linux多线程

1.线程

    线程概念:linux下,线程以进程的pcb模拟实现,因此linux下的线程就是pcb,是一个轻量级进程;

    简单谈一谈线程:说到线程概念,必须先说到进程。进程是一个运行中的程序,在操作系统中,一个程序运行起来,程序被加载到内存中,操作系统创建一个进程描述符(进程控制块),PCB对程序的运行进行描述控制,因此进程就是pcb,在linux是task_struct结构体。

    线程与进程:

        1.linux线程使用进程pcb模拟实现,因此linux线程是一个轻量级进程;如果说pcb成为了线程,那么进程就是线程组,一个进程至少有一个或多个线程。

        2.因为linux下pcb是一个线程,因此线程是cpu调度的基本单位。

        3.因为程序运行的时候分配进程资源,进程(线程组)是资源分配的基本单位。

    一个进程中的多个线程

        前提:进程中的所有线程共用一个虚拟地址空间,因此共享进程的代码段,数据段

        共享数据:文件描述符表,每种信号的处理方式(SIG_IGN、SIG_DFL或者自定义的信号处理函数)

                          当前工作目录、用户id和组id

        独有数据:因为每个线程都是pcb,是cpu调度的基本单位,因此线程可以同时运行,但是不会造成调用栈混乱,主要因为每个线程都有自己独有的数据(线程ID、寄存器、栈、errno、信号屏蔽字、调度优先级)

    线程优缺点

        多线程优点:因为多线程共享虚拟地址空间

            1.线程间通信简单

            2.线程的创建/销毁成本更低

            3.线程的调度成本更低

            4.线程的执行粒度更细

        多线程缺点:

           1.线程缺乏访问控制----exit退出的是整个进程

           2.健壮性较低----线程的某些错误会导致整个进程的退出(对多个进程共享的数据做出不同的操作,导致数据出现异常)

           3.性能损失----多线程共享一个处理器,若线程数量比可用的处理器数量多,可能会有较大的性能损失(性能损失指增加了额外的同步和调度开销)

           4.编程难度提高----编写与调试一个多线程程序比单线程程序困难得多

2.线程创建

    Linux多线程_第1张图片

     查看一个线程ID

    

    ps命令中-L选项,会显示信息:

        LWP:线程ID,既gettid()系统调用的返回值

        NLWP:线程组内线程的个数

    一个线程中有三个标识符:tid、pid、tgid

        tid:线程在线程组中虚拟地址映射的地址的首地址

        pid:创建一个线程的ID,就是线程ID

        tgid:进程ID

[sun@ssh thread]$ cat creat.c
/*************************************************************************
	> File Name: creat.c
	> Author: ssh
	> Mail: [email protected] 
	> Created Time: Fri 19 Apr 2019 09:06:42 AM CST
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 

void *thr_start(void *arg){
	//pthread_t pthread_self(void);
	//	返回调用线程的线程id
	pthread_t tid = pthread_self();
	while(1){
		printf("chlid thread-----%d tig:%p\n", arg, tid);
		sleep(1);
	}
}

int main(int argc, char *argv[])
{
	//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
	//	void *(*start_routine)(void *), void *arg);
	//	thread:	于保存返回的线程ID
	//	attr:	线程属性,通常置NULL
	//	start_routine:	线程入口函数
	//	arg:	给线程传入的参数
	//	返回值:成功:0	失败:errno
	pthread_t tid;
	int ret = pthread_create(&tid, NULL, thr_start, (void*)666);
	if(ret != 0){
		printf("pthread create error\n");
		return -1;
	}

	while(1){
		printf("main thread----chlid tid:%p pid:%d\n", tid, getpid());
		sleep(1);
	}
    return 0;
}

3.线程终止

    不终止整个进程,只终止某个线程有三种方法:

       1.从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

       2.线程可以调用pthread_exit终止自己。

       3.一个线程可以调用pthread_cancel终止同一进程中的另一个线程。(指定线程退出)

/*************************************************************************
	> File Name: exit.c
	> Author: ssh
	> Mail: [email protected] 
	> Created Time: Fri 19 Apr 2019 03:02:13 PM CST
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 

void *thr_start(void *arg){
	//pthread_t pthread_self(void);
	//	返回调用线程的线程id
	pthread_t tid = pthread_self();
	while(1){
		printf("child thread----%d--tid:%p\n", arg,tid);
		sleep(1);
		//void pthread_exit(void *retval);
		//	退出调用线程,并返回retval
		//	线程的返回值,不仅可以判断终止场景,并且可以获取任务处理结果
		pthread_exit("leihoua~~");
		//eixt(0);	exit退出的是整个进程(所有线程都会退出)
	}
	return NULL;
} 

int main(int argc, char *argv[])
{
	//int pthread_create(pthread_t *thread, const pthread_attr_t *attr
	//	void *(*start_routine)(void*), void *arg);
	//	thread:	用于保存返回的线程ID
	//	attr:线程属性,通常置NULL
	//	start_routine:	线程入口函数
	//	arg:	给线程传入参数
	//	返回值:	成功: 0		失败:	errno
	pthread_t tid;
	int ret = pthread_create(&tid, NULL, thr_start, (void*)666);
	if(ret != 0){
		printf("pthread create error\n");
		return -1;
	}
	sleep(1);
	//int pthread_cancel(pthread_t thread);
	//	取消指定线程---让指定线程退出
	//	thread:线程id
	//pthread_cancel(pthread_self());
	while(1){
		printf("man thread-----child tid:%p pid:%d\n", tid, getpid());
		sleep(1);
		//pthread_exit(NULL);
	}
    return 0;
}

4.进程等待:等待指定线程退出---获取指定线程返回值,回收线程资源

    为什么需要线程等待?

        1.已经退出的线程,其空间中没有被释放,仍然在进程的地址空间内。

        2.创建新的线程不会复用刚才退出线程的地址空间。

    注意:一个线程被创建,默认有一个属性是joinable;处于这个属性线程必须被等待,因为线程退出后,不会自动回收资源,造成内存泄露;

/*************************************************************************
	> File Name: join.c
	> Author: ssh
	> Mail: [email protected] 
	> Created Time: Fri 19 Apr 2019 03:44:47 PM CST
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 

void *thr_start(void *arg){
	sleep(3);
	return "abc";
}

int main(int argc, char *argv[])
{
	pthread_t tid;
	int ret = pthread_create(&tid, NULL, thr_start, NULL);
	if(ret != 0){
		printf("pthread create error\n");
		return -1;
	}
	//int pthread_join(pthread_t thread, void **retval);
	//	阻塞等待线程退出
	//	thread:	等待的线程ID
	//	retval:	用户获取线程的退出的返回值
	char *retval;
	pthread_join(tid, (void**)&retval);
	printf("retval:%s\n", retval);
	while(1){
		printf("i am host thread~~\n");
		sleep(1);
	}
    return 0;
}

5.线程分离

    分离一个线程将线程的joinable属性修改为detach属性,处于这个属性的线程,退出后将自动回收资源,处于这个属性的线程,不能被等待,否则报错;

    thread线程以不同的方式终止,通过pthread_join得到的终止状态时不同的:

        1.如果thread线程通过return返回,value_ptr所指向的单元存放的是threa线程函数的返回值;

        2.如果thread线程被别的线程调用pthread_cancel异常终止,value_ptr所指向的单元里存放的是常数PTHREAD_CANCELED;

        3.如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数;

        4.如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。

    注意:线程分离可以在任意线程任意时间分离,包括分离自己

/*************************************************************************
	> File Name: join.c
	> Author: ssh
	> Mail: [email protected] 
	> Created Time: Fri 19 Apr 2019 03:44:47 PM CST
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 

void *thr_start(void *arg){
	//pthread_detach(tid);
	// tid:线程在线程组中虚拟地址的首地址
	pthread_detach(pthread_self());
	sleep(3);
	return "abc";
}

int main(int argc, char *argv[])
{
	pthread_t tid;
	int ret = pthread_create(&tid, NULL, thr_start, NULL);
	if(ret != 0){
		printf("pthread create error\n");
		return -1;
	}
	//int pthread_join(pthread_t thread, void **retval);
	//	阻塞等待线程退出
	//	thread:	等待的线程ID
	//	retval:	用户获取线程的退出的返回值
	/*
	char *retval;
	pthread_join(tid, (void**)&retval);
	printf("retval:%s\n", retval);
	*/
	while(1){
		printf("i am host thread~~\n");
		sleep(1);
	}
    return 0;
}

6.线程安全:因为多个执行流之间对数据的竞争操作---不安全操作,有可能造成数据二义性

    如何实现线程安全:同步与互斥

        同步:对临界资源访问的时序可控

        互斥:对临界资源同一时间的唯一访问

    如何实现同步与互斥:

    互斥的实现:互斥锁(mutex)

        pthread_mutex_t 创建一个互斥锁变量

        pthread_mutex_init()  互斥锁初始化

        pthread_mutex_lock()  加锁

        pthread_mutex_unlock()  解锁

        pthread_mutex_destroy()  互斥锁销毁

    死锁:

    死锁是如何产生的:四个必要条件

        互斥条件 

        不可剥夺条件

        请求与保持条件

        环路等待条件

    死锁的预防:破坏必要条件------>如何破坏必要条件

    死锁的避免:银行家算法,死锁检查算法 ----->链接:死锁的产生、预防、避免、检测和解除

  

7.条件变量:用于实现线程间同步----唤醒与等待

    条件满足则唤醒等待的线程,条件不满足则等待

        pthread_cond_t 创建一个条件变量

        pthread_cond_init() 条件变量初始化

        pthread_cond_wait() 等待

        pthread_cond_signal() 唤醒

        pthread_cond_destroy() 销毁

    为什么pthread_cond_wait需要互斥锁?

        如果有多个角色,需要使用多个条件变量分别等待,不能等待在条件变量上,有可能出现唤醒角色错误的问题。

        条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护。没有互斥锁就无法完全的获取和修改共享数据。

8.生产者与消费者模型:一个场所,两个角色,三种关系

#include 
#include 
#include 
#include 
#include 
class BlockQueue{
	private:
		std::queue _queue;
		int _capacity;
		pthread_mutex_t _mutex;
		pthread_cond_t _productor;
		pthread_cond_t _customer;
	private:
		void QueueLock(){
			pthread_mutex_lock(&_mutex);
		}

		void QueueUnLock(){
			pthread_mutex_unlock(&_mutex);
		}
		
		void ProductorWait(){
			pthread_cond_wait(&_productor, &_mutex);
		}

		void ProductorWakeUp(){
			pthread_cond_signal(&_productor);
		}

		void CustomerWait(){
			pthread_cond_wait(&_customer, &_mutex);
		}

		void CustomerWakeUp{
			pthread_cond_signal(&_customer);
		}

		bool QueueIsEmpty(){
			return _queue.empty();
		}

		bool IsFull(){
			return (_queue.size() == _capacity);
		}
	public:
		BlockQueue(){
			pthread_mutex_init(&_mutex, NULL);
			pthread_cond_init(&_productor, NULL);
			pthread_cond_init(&_customer, NULL);
		}
		
		~BlockQueue(){
			pthread_mutex_destory(&_mutex);
			pthread_cond_destory(&_productor);
			pthread_cond_destory(&_customer);
		}
		
		void Productor(int val){
			QueueLock();
			if(QueueIsFull()){
				ProductorWait();
			}
			_queue.push(val);
			QueueUnLock();
			CustomerWakeUp();	
		}

		void Customer(int *val){
			QueueLock();
			if(QueueIsEmpty()){
				CustomerWait();
			}
			*val = _queue.front();
			_queue.pop();
			QueueUnLock();
			ProductorWakeUp();
		}
};

9、POSIX信号量

    POSIX信号量与SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的,但是POSIX可以用线程间同步。

    初始化信号量:sem_init(sem_t *sem, int pshared, unsigned int value);

                              pshared:0表示线程间共享,非零表示进程间共享;

                              value:信号量初始值

    销毁信号量:sem_destory(sem_t *sem);

    等待信号量:sem_wait(sem_t *sem);    将信号量的值减一

    发布信号量:sem_post(sem_t *sem);  表示资源使用完毕,可以归还资源

10、读写锁

    使用场景:资源共享,两种操作,一种操作频繁使用,另一种操作使用的比较少;

    注意:写独占,读共享,写锁优先级高

    初始化:pthread_rwlock_init(pthread_rwlock_t *rwlock, (属性一般为NULL));

    销毁:pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

     加锁和解锁: pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

                            pthread_rwlock_wrlock(pthread_relock_t *rwlock);

                            pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

 

 

你可能感兴趣的:(Liunx)