有了进程,为何还要引入线程呢?使用多线程有哪些好处?
1、和进程相比,线程是一种非常节俭的多任务操作方式,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表维护它的代码段、堆栈段、数据段,这是一种昂贵的多任务工作方式
运行于一个进程中的多线程,它们之间使用相同的地址空间,而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间
2、线程间方便的通讯机制,对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便
3.除了以上说的有点外,多线程程序作为一种多任务、并发的工作方式,有如下优点:
使用CPU系统更加高效,操作系统会爆炸当线程数不大于CPU数码时,不同的线程运行与不同的CPU上
改善程序结构,一个既长又复杂的进程可以考虑分为多线程,称为几个独立或半独立的运行部分,这样的程序会利于理解和修改
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接(编译)时需要加libpthread.a
创建线程时候一系列参数传递
首先创建线程成功后 会立马调用创建函数第三个参数即线程的工作函数
此时如果线程有参数要传递给工作函数 可用第四个参数
工作函数完成以后 主要有两种退出方式 1)使用pthread_exit()函数 此时会将()中的值传递给主线程中的join()函数第二个参数
2)使用return (void)8 会将8传递给join()函数第二个参数*
函数返回值代表成不成功
第一个参数是指针 会带回线程的ID
第三个参数是一个指向函数的指针 所以赋函数名
创建一旦成功 会立刻执行第三个函数的功能 此时就是两个线程并发的执行
如果主线程要传值给子线程函数 就写第四个参数
线程退出可以使用上面的函数 而这个函数()中的参数就是传递给join函数的第二个参数的
线程退出也可以return 但要记得要转换为(void*)
#include
#include
#include
#include
#include
char mem[] = "hello world";//全局变量
void * thread_work(void * arg){//子线程的工作函数 arg是子线程传给工作函数的值
printf("child thread running .. get arg is %s\n",(char *)arg);//输出参数
sleep(3);
strcpy(mem,"bye");//对全局变量操作
pthread_exit("thanks for you cup time!\n");//退出子线程 并将括号中的字符串传给join函数
}
int main(){
pthread_t tid;//线程ID
int ret;
void *thread_rel;//存放jion函数得到的返回值
ret = pthread_create(&tid,NULL,thread_work,(void *)mem);//创建线程
if(ret != 0){
perror("create thread error!");
exit(-1);
}
printf("father thread waiting...\n");
pthread_join(tid,&thread_rel);//等待子线程结束
printf("father thread joined get thread_rel is %s\n",(char *)thread_rel);//输出子线程传递的参数
printf("select share men is %s\n",mem);//输出共享变量 即全局变量
return 0;
}
进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决
线程之间对资源的竞争:
a)互斥量Mutex
b)信号灯Semaphore
c)条件变量Conditions
为什么要使用互斥量?
当一个线程在使用共享变量即全局变量时 无法进行独占 从而会导致一系列问题
对于这种情况,系统给我们提供了互斥量,线程在取出头结点前必须要等待互斥量,如果此时有其他线程已经获得该互斥量,那么该线程将会阻塞在这里,只有等到其他线程释放掉该互斥量后,该线程才有可能得到该互斥量,互斥量从本质上说就是一把锁,提供对共享资源的保护访问
创建语句 pthread_mutex_t lock;
创建lock锁成功
强调!!! 创建互斥锁是在所有函数外部 一定要将互斥锁设为全局变量 因为其也是共享资源
创建互斥量成功后 在主线程中进行初始化 一般使用动态初始化
pthread_mutex_init(&lock,NULL)第二个参数NULL表示普通锁
强调!!!所有参数都要取地址 因为函数参数是指针
第一个参数放互斥锁的地址
第二个参数 指定互斥锁的属性 一般情况下 写NULL 表示默认属性
锁值的属性有PTHREAD_MUTEX_TIMED_NP这是默认属性 这个锁是普通锁 同一个线程只能锁一次
第二个属性有PTHREAD_MUTEX_RECURSIVE_NP允许**同一个**线程对资源多次上锁 多次上锁必须多次解锁
第三个属性PTHREAD_MUTEX_ERRORCHECK_NP检测锁 不允许多次锁 会报错
第四种属性PTHREAD_MUTEX_ADAPTIVE_NP等待解锁以后给其余线程竞争
初始化失败返回-1 成功返回0
在线程工作函数需要使用共享资源 则在使用之前上锁
pthread_mutex_lock(&lock)
lock是阻塞等待
trylock是非阻塞等待 这时没有锁成功 返回值是-1
在操作完成后,必须给互斥量解锁也就是前面所说的释放。这样其他等待该锁的线程才有机会获得该锁,否则其他线程将会永远阻塞
解锁:pthread_mutex_unlock(&lock)
互斥锁是一个共享变量(全局变量)
销毁:pthread_mutex_destroy(&lock)
当使用互斥锁时 形成了死锁解不开时 需要条件变量从中调解 解除死锁状态
何为死锁 条件变量如何工作 例如:
当消费者要在货架上买东西 当消费者线程工作函数进行买之前要将共享资源 货架 锁起来
所以对其上锁 锁好后 要判断条件 货架有没有货 当有货时 条件满足 执行买的操作 操作完成后解锁 让生产者往货架上放东西
然而如果上锁后发现 货架为空 那么条件不满足 买不了东西 自然解不了锁 那么就会进入死锁状态
这时候 条件变量其效果 pthread_cond_wait(&cond,&lock)
这个函数 会悄悄的将锁解开 然后开始等待 等待其他线程去拿锁 然后工作 工作完成后 会给这个函数发送一个信号 收到货架不空的信号后 wait函数会将共享资源锁住给消费者工作函数使用 这时候 条件满足 锁也上了 可以执行买的操作 然后解锁 当然这时候要给别的线程的条件变量wait函数发送信号 货架不满的信号 因为 生产者也可能会进入死锁 也可能需要条件变量来调解
条件变量的创建 初始化 与互斥量差不多
wait函数会在等待过程中将锁解开 然后等待条件成立 条件成立后再上锁
Linux的有点之一就是在于它丰富而稳定的网络协议栈,其范围是从协议无关层(如通用的socket)