线程的基本操作
概念
线程是程序中的一个执行路线。每个程序当中至少有一个线程。
程序在执行的过程中是逐条执行的,按照代码的逻辑一次向下执行,所以无法同时完成两条指令,故而引进了线程,举个很简单的例子,如果同时进行两个死循环,用单线程的话只能进行一个死循环,另一个死循环永远也不会执行,故而用多线程就可以解决这个问题。在学习网络cs模型时更能体现线程的作用,因为你需要在发送数据的同时接收数据。
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);参数thread:返回线程IDattr:设置线程的属性,attr为NULL表示使用默认属性start_routine:是个函数地址,线程启动后要执行的函数arg:传给线程启动函数的参数返回值:成功返回0;失败返回错误码
获取线程ID
pthread_t pthread_self(void)返回值返回调用该函数的线程的ID
线程终止
void pthread_exit(void *value_ptr);
参数value_ptr:value_ptr不要指向一个局部变量。
该函数无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)int pthread_cancel(pthread_t thread);
参数thread:线程ID
返回值:成功返回0;失败返回错误码
线程等待
int pthread_join(pthread_t thread, void **value_ptr);
参数thread:线程IDvalue_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
为何要线程等待?线程退出时,其占有的空间资源并不会释放掉,在次创建一个线程时该空间资源并不会被再次利用,也就是说会造成内存泄漏,故而需要线程等待。其中线程等待函数中的参数value_ptr指向线程ID为thread的线程返回值。如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED;如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
线程分离
int pthread_detach(pthread_t thread);
参数thread:线程ID
返回值:成功返回0,错误返回错误号
线程分离与线程等待很相似,他们都能释放掉已结束线程占用的空间资源,但线程分离不关心线程退出的返回值,只要线程退出,就自动释放线程资源。
编译指令
gcc 123.c -o out -lpthread
注:编译多线程的程序一定要加上 -lpthread
示例:
#include<pthread.h>
#include<stdio.h>
#include<unistd.h>
void *fun(void *num)
{int i=0;for(;i<30;i++){printf("a");if(i==25){pthread_exit(NULL);//输出25个a后线程终止}usleep(1);//延时1us}
}int main()
{pthread_t ptid;if(pthread_create(&ptid,NULL,fun,NULL)!=0)//创建线程{return 0;}int i=0;for(;i<10;i++){if(i%2==0){printf("b");}usleep(2);//延时2us}printf("\n");pthread_join(ptid,NULL);线程等待return 0;
}
结果
主线程在执行5个b后,输出结束,此时等待子线程结束,即第二行结果显示,至于第一行为什么a、b都有输出,是因为线程在执行时是同时进行的,主线程输出一个b后睡眠2us,在这段时间子线程开始输出a,因为子线程也有1us睡眠,限制其执行速度,当主线程的2us时间到后,又开始主线程输出b。
线程互斥
我们先看看几个概念:
临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作
用
原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
那到底什么是线程互斥呢?不要着急,我们先看看下面这个抢票的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>int ticket = 100;//共有100张票void *route(void *arg)
{char *id=(char *)arg;while(1){if(ticket>0){usleep(1000);printf("%s sells ticket:%d\n",id,ticket);ticket--;//抢到票就减1}else{break; }}
}
int main()
{pthread_t t1,t2,t3,t4;pthread_create(&t1,NULL,route,(void *)"thread 1");pthread_create(&t2,NULL,route,(void *)"thread 2");pthread_create(&t3,NULL,route,(void *)"thread 3");pthread_create(&t4,NULL,route,(void *)"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);return 0;}
结果
结果让人感到意外,怎么会有负数呢?因为线程是同时进行的,当进行到票数为1的时候,此时部分线程已经经过了**if(ticket>0)**的判断,其中有一个线程的执行速度比较快,他抢先拿到了最后的一张票,此时票数为0,因为此时并不止这一个线程经过了判断,他们也能强到一张票,故而出现负数。原理图如下:
为避免上述现象的出现,引进了互斥量mutex
互斥量初始化
静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);参数:mutex:要初始化的互斥量attr:NULL
互斥量加锁与解锁
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
注:加锁后一定要记得解锁二者的参数相同,都是指初始化后的互斥量
互斥量销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:需要销毁的互斥量
注:静态分配的互斥量不需要销毁,已经枷锁的互斥量不能销毁
使用方法
- 在使用前先初始化
- 访问临界资源前加锁
- 访问临界资源后解锁
- 互斥量使用结束后销毁
改进后代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>int ticket = 100;//票
pthread_mutex_t mutex;//定义互斥量void *route(void *arg)
{char *id=(char *)arg;while(1){pthread_mutex_lock(&mutex);//加锁if(ticket>0){printf("%s sells ticket:%d\n",id,ticket);ticket--;pthread_mutex_unlock(&mutex);//解锁}else{pthread_mutex_unlock(&mutex);//解锁break;}usleep(2);}
}
int main()
{pthread_t t1,t2,t3,t4;pthread_mutex_init(&mutex,NULL);//初始化互斥量pthread_create(&t1,NULL,route,(void *)"thread 1");pthread_create(&t2,NULL,route,(void *)"thread 2");pthread_create(&t3,NULL,route,(void *)"thread 3");pthread_create(&t4,NULL,route,(void *)"thread 4");pthread_join(t1,NULL);pthread_join(t2,NULL);pthread_join(t3,NULL);pthread_join(t4,NULL);pthread_mutex_destroy(&mutex);//互斥量销毁return 0;}
结果