Linux多线程编程——多线程与线程同步

时间:2023-06-14 17:17:48

多线程

使用多线程好处:

一、通过为每种事件类型的处理单独分配线程,可以简化处理异步事件的代码,线程处理事件可以采用同步编程模式,启闭异步编程模式简单

二、方便的通信和数据交换

由于进程之间具有独立的数据空间,多进程必须使用操作系统提供的复杂机制才能实现内存和文件描述符的共享,导致上下文切换的开销比较大。而线程之间共享进程的所有资源,所以,多线程可以访问相同的存储空间和文件描述符。

三、对一些不具有互斥型的问题,可以将其分解为从而改善程序的吞吐量。对于进程,在完成多个任务时,实际上需要将任务串行化。对于多线程,相互独立而且想不不依赖的任务可以交叉运行,只需要为每个任务分配一个线程即可。

四、交互的程序可以通过使用多线程实现响应时间的改善,多线程可以把程序中用于处理用户输入输出的部分与其他部分分开。

线程包含了进程内执行环境必须的信息,包括进程ID,一组寄存器值,栈,调度优先级和策略,信号屏蔽字,errno变量以及线程私有数据。

线程ID使用pthread_t数据类型来表示,类似于一种结构体,可以使用 pthread_self 函数和 pthread_equal 函数来通过线程ID识别线程。

原型:

#include <pthread.h>

int pthread_eaual(pthread_t tid1,pthread_t tid2);   //判断两个线程ID是否相等

相等返回非0,不相等返回0

#include <pthread.h>

pthread_t pthread_self(void);   //获取自身线程ID

返回调用线程的ID

线程创建

#include <pthread.h>

int pthread_creat(pthread_t *restrict tidp,const pthread_attr_t *attr,void*(*start_rtn)(void),void *restrict arg);   //创建线程

成功返回0,否则返回错误编号

tidp 指向的内存单元被设置为新创建的现成的线程ID

attr 用于定制线程的线程属性,设置为NULL时,则使用默认属性

新创建的函数从strat_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg.若要想向strat_rtn传递不知一个参数,可以将多个参数放在一个结构体中,然后把结构体的地址作为arg参数传入。

线程终止

如果进程中的任一线程调用了exit,_Exit或_exit,则整个进程会终止。同样,如果信号的默认动作是终止进程,那么,把信号发送到进程会终止整个进程。

单个进程的退出方式有三种:

1、线程只是从启动的例程中退出,返回值是线程的终止码;

2,、线程可以被统一进程中的其他线程取消;

3、线程调用pthread_exit;

#include<pthread.h>

void pthread_exit(void *rval_ptr);

线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用exit函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针

rval_ptr是一个无类型的指针,与传递给启动例程的单个参数类似,可以通过进程中的其他线程调用pthread_join函数访问这个指针。

#include<pthread.h>

int pthread_join(pthread_t thread,void **rval_ptr);

成功返回0;否则返回错误编号

pthread_join()函数,以阻塞的方式等待thread指定的线程调用pthread_exit、冲启动例程中返回或者被取消。当函数返回时,被等待线程的资源被收回。

如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

thread: 线程标识符,即线程ID,标识唯一线程。rval_ptr: 用户定义的指针,用来存储被等待线程的返回值。

如果线程是从启动例程中返回,

rval_ptr将包含返回码,如果线程被取消,由rval_ptr指向的内存单元被置为PTHREAD_CANCELED。

线程同步

线程同步机制包括互斥读写锁以及条件变量

互斥

可以把互斥变量之置为常量PTHREAD_MUTEX_INITIALIZER(针对静态分配的互斥量),或调用pthread_mutex_init函数进行初始化。如果动态的分配互斥量(如调用malloc函数),那么在释放内存前需要调用pthread_mutex_destory。

#include<pthread.h>

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

int  pthread_mutex_destory(pthread_mutex_t  mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

成功返回0,否则返回错误编号

如果线程不希望被堵塞,可以调用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,则pthread_mutex_trylock将互斥量锁住,不会出现阻塞并返回0,否则失败并返回EBUSY。

读写锁

初始化

#include<pthread.h>

int pthread_rwlock_init(pthread_rwlock_t  *restrict rwlock, const pthread_rwlockattr_t *restrict attr);   //初始化

int pthread_rwlock_destory(pthread_rwlock_t  *restrict rwlock);   //销毁读写锁,

成功返回0,错误返回错误编号;

锁定读写锁与解锁

#include<pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t  rwlock);   //在读模式下锁定读写锁

int pthread_rwlock_wrlock(pthread_rwlock_t  rwlock);   //在写模式下锁定读写锁

int pthread_rwlock_unlock(pthread_rwlock_t  rwlock);   //解锁

成功返回0,错误返回错误编号;

条件变量

和互斥量一样,可以把条件变量之置为常量PTHREAD_COND_INITIALIZER(针对静态分配的条件变量),或调用pthread_cond_init函数进行初始化。在释放内存前需要调用pthread_cond_destory函数对变量进行去除初始化。

#include<pthread.h>

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

int  pthread_cond_destory(pthread_cond_t cond);

成功返回0,否则返回错误编号

条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足调用条件等待函数就等待条件满足。

#include<pthread.h>

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

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

成功返回0,否则返回错误编号

cond是一个指向条件变量的指针,mutex是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量。传递给pthread_cond_wait的互斥量对条件进行保护,调用者吧锁住的互斥量传递给函数,函数把调用线程防盗等待条件的线程列表上,然后互斥量解锁,pthread_cond_wait返回时,互斥量再次被锁住。

pthread_cond_timedwait与pthread_cond_wait工作方式相似,只是多了一个timeout。timeout是一个绝对值而不是相对值。其为需要等待的时间加上当前时间,即未来某一时刻。

timeout通过timespec结构指定:

struct timespec{

time_t    tv_sec;

time_t    tv_nsec;

};

可以使用gettimeofday获取用timeval表示的当前时间,然后把等待时间转化成timespec结构。

可以使用一下函数得到timeout值得绝对时间

void maketimeout(struct timespec *tsp,long minutes)

{

struct timeval now;

gettimeofday(&now); //获取当前时间

tsp->tv_sec=now.tv_sec;

tsp->tv_nsec=now.tv_usec*1000;   //usec转换成nsec

tsp->tv_sec+=minutes*60;

}

pthread_cond_signal和pthread_cond_broadcast两个函数用来通知线程条件已经满足。

pthread_cond_signal在条件满足后唤醒等待条件的某一个线程,而pthread_cond_broadcast将唤醒所有线程。

#include<pthread.h>

int pthread_cond_signal(pthread_cond_t  *cond);

int pthread_cond_broadcast(pthread_cond_t  *cond);

成功返回0,否则返回错误编号

注意:一定要在条件状态改变以后再给线程发信号