目录
一、线程的概念
1. 为什么引入线程
首先,每个进程都拥有自己的数据段、代码段和堆栈段,进程的体积庞大,在进程切换的时候操作系统开销较大,需要不断刷新高速缓存(Cache)。
为了能进一步减少处理器的空转时间和资源消耗,操作系统引入了线程这一概念,也称轻量级进程。它是进程内独立的一条运行路线,是内核调度的最小单位。
2. 线程的特点
在同一个进程中创建的所有线程共享该进程的地址空间,Linux里同样用task_struct来描述一个线程,线程和进程都参与统一的调度。
一个进程中的多个线程共享以下资源:
- 可执行的指令
- 静态数据
- 文件描述符表
- 用户ID、用户组ID
- 信号处理函数
每个进程私有的资源为:
- 线程ID(TID)
- PC程序计数器
- 堆栈
- 执行状态、属性和优先级等
二、线程相关接口函数
在linux用户空间中线程的操作函数通常使用pthread线程库,是一个遵循POSIX标准的通用的线程库,具有良好的可移植性。
链接这些线程函数库时要使用编译器命令的“-lpthread”选项
(1)创建线程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数:thread :线程对象,一个线程对应一个线程对象,也称线程标识符
attr :线程属性,默认属性填NULL,表示结合属性
start_routine :线程处理函数
//线程处理函数最多传一个参数,可不传。如果要接收多种数据,用一个结构体表示,并用结构体指针作为参数传入
arg : 给线程处理函数传参,如果线程处理函数没有参数,则填NULL
返回值:成功则返回0,失败返回错误编号
注:线程创建之后,就开始执行相应的线程处理函数,并在该函数运行完之后,该线程结束。使用ps -aL 命令查看轻量级进程。
(2)退出线程
#include <pthread.h>
void pthread_exit(void *retval);
参数:retval :线程结束信息,由pthread_join等待接收,如果不想接收信息则填NULL。
注:
- 在线程中使用pthread_exit函数是主动退出线程。但不要在线程中使用exit函数进行退出,exit是让当前进程终止,在线程中使用会导致该进程中所有线程都结束。
- 进程又称主线程(main thread),在主线程中使用pthread_exit函数不会退出整个进程,但是会让主线程结束,导致子线程还在,内存无法被回收,成为僵尸进程。为防止出现主线程先于子线程结束的情况,引入的等待线程即pthread_join函数。
- 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
- 创建新的线程不会复用刚才退出线程的地址空间。
(3)等待线程
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
//等待线程一般在主线程中调用,且为阻塞函数
参数:
thread : 线程对象
retval :线程结束信息
返回值:成功则返回0,失败返回错误编号
线程使用例子:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct data
{
char name[32];
int age;
}D;//用于测试线程处理函数的传参
void *func1(void *arg);
void *func2();
int main(int argc, char *argv[])
{
pthread_t thread1;
pthread_t thread2;
void * result1;
void * result2;
D stu={"xm",10};
int ret=pthread_create(&thread1,NULL,func1,&stu);
if(ret<0)
{
perror("pthread_create1");
exit(-1);
}
ret=pthread_create(&thread2,NULL,func2,NULL);
if(ret<0)
{
perror("pthread_create1");
exit(-1);
}
int n=5;
while(n--)
{
/* if(n==3)
{
pthread_exit(NULL);//退主线程只是结束主线程的后续执行代码,但进程空间仍然存在,子线程会继续运行
}
*/
printf("main thread \n");
sleep(1);
}
pthread_join(thread1,&result1);
printf("%s,阻塞结束\n",(char *)result1);
puts("===========");
pthread_join(thread2,&result2);
printf("%s,阻塞结束\n",(char *)result2);
return 0;
}
void *func1(void *arg)
{
D *q=(D*)arg;
int n=3;
while((q->age)--)
{
if(q->age==7)
{
pthread_exit("thread1 end");
}
printf("child thread1 \n");
sleep(1);
}
return NULL;
}
void *func2()
{
int n=7;
while(n--)
{
printf("cccccc\n");
sleep(1);
}
pthread_exit("thread2 end-----");
return NULL;
}
三、线程间的同步与互斥
1.同步
定义:多个线程之间按照事先约定好的顺序有先后地完成某个事件。
信号量:为系统中一种资源,本质上是一个非负整数。信号量的值等于资源个数。
操作信号量只能由特定函数接口才能访问:
1)信号量的初始化 ——sem_init();
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem :信号量对象
pshared : 用于线程间同步,填0
value : 信号量初始值
返回值:成功返回0,失败返回-1;
2)P操作(申请资源)——sem_wait();
函数结构:
if(是否有资源)
{
执行后续代码;
信号量-1;
}
else
{
阻塞等待,直到有资源唤醒为止
}
#include <semaphore.h>
int sem_wait(sem_t *sem);
参数:
sem : 信号量对象
返回值:
3)V操作(释放资源)——sem_post();
函数结构:
if(没有线程在等待该资源)
{
信号量+1;
}
else
{
唤醒第一个等待的线程,让其继续运行
}
#include <semaphore.h>
int sem_post(sem_t *sem);
参数:
sem : 信号量对象
返回值:成功则返回0,失败返回-1且信号量值保持不变
2.互斥
当一个线程使用公共数据时,其他线程都不能访问该公共数据。
临界资源:多个线程能够访问的数据
临界区:涉及到临界资源的代码模块
互斥是使用互斥锁保护临界资源(区)。线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
1)创建互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr)
参数:
mutex : 锁对象
attr :互斥锁属性,默认填NULL表示缺省属性
返回值:成功返回0,失败返回-1;
2)申请锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
参数:
mutex : 锁对象
返回值:成功返回0,失败返回-1;
3)释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex)
参数:
mutex : 锁对象
返回值:成功返回0,失败返回-1;
互斥锁使用例子:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <semaphore.h>
void *fun_gets();
void *fun_puts();
pthread_mutex_t mutex;
int num1=0,num2=0;
int count=0;
int main(int argc, char *argv[])
{
pthread_mutex_init(&mutex,NULL);
pthread_t thread1,thread2;
void *ret_th1,*ret_th2;
int ret;
ret=pthread_create(&thread1,NULL,fun_gets,NULL);
if(ret<0)
{
perror("pthread_create1");
return -1;
}
ret=pthread_create(&thread2,NULL,fun_puts,NULL);
if(ret<0)
{
perror("pthread_create2");
return -1;
}
pthread_join(thread1,&ret_th1);
printf("%s\n",(char *)ret_th1);
pthread_join(thread2,&ret_th2);
printf("%s\n",(char *)ret_th2);
return 0;
}
void *fun_gets()
{
while(1)
{
pthread_mutex_lock(&mutex);
num1=count;
num2=count;
count++;
pthread_mutex_unlock(&mutex);
}
pthread_exit("thread1 end");
return NULL;
}
void *fun_puts()
{
while(1)
{
pthread_mutex_lock(&mutex);
if(num1!=num2)
{
printf("num1=%d num2=%d\n",num1,num2);
}
pthread_mutex_unlock(&mutex);
}
pthread_exit("thread2 end");
return NULL;
}
该程序运行时,thread1和thread2线程访问理解资源数据num1、num2和count。如果不对线程内的临界区上互斥锁,就会导致num1和num2出现值不相等情况,比如刚执行num1=count完后时间片结束,轮到thread2线程得到调用,就会出现num1 != num2的情况。