Linux 线程的创建与同步
线程的概念
线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。
进程与线程的区别
- 进程是资源分配的最小单位,线程是 CPU 调度的最小单位
- 进程有自己的独立地址空间,线程共享进程中的地址空间
- 进程的创建消耗资源大,线程的创建相对较小
- 进程的切换开销大,线程的切换开销相对较小
线程的实现方式
在操作系统中,线程的实现有以下三种方式:
- 内核级线程
- 用户级线程
- 组合级线程
Linux 中线程的实现
Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把
所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来
表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯
一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和
其他一些进程共享某些资源,如地址空间)。
线程库中的接口介绍
pthread_create()用于创建线程
int pthread_create(pthread_t *thread, **const ** pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
成功返回 0, 失败返回错误码
thread: 接收创建的线程的 ID
attr: 指定线程的属性
start_routine: 指定线程函数
arg: 给线程函数传递的参数
pthread_exit()退出线程
int pthread_exit(void *retval);
pthread_exit()退出线程
retval:指定退出信息
pthread_join()等待 thread 指定的线程退出,线程未退出时,该方法阻塞
int pthread_join(pthread_t thread, void **retval);
retval:接收 thread 线程退出时,指定的退出信息
测试代码1
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>void* fun(void* arg)
{int* p = (int*)arg;int val = *p;printf("%d\n",val);return NULL;
}int main()
{pthread_t id[5];int i=0;for(;i<5;i++){pthread_create(&id[i],NULL,fun,(void*)&i);}for(i=0;i<5;i++){pthread_join(id[i],NULL);}exit(0);
}
预估:会出现0 1 2 3 4按一定顺序
结果如图:
我们发现结果和预想的不一样,这是因为线程是并发运行的,主线程main()和5个副线程都在同时运行,fun是通过指针解引用,主线程会随时改变i的值 ; pthread_create(&id[i],NULL,fun,(void*)&i);只是向内核申请,不一定顺序批准。
测试代码2
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
int val = 0;
void* fun(void* arg)
{for(int i=0;i<1000;i++){printf("%d\n",++val);}return NULL;
}
int main()
{pthread_t id[5];int i=0;for(;i<5;i++){pthread_create(&id[i],NULL,fun,(void*)&i);}for(i=0;i<5;i++){pthread_join(id[i],NULL);}exit(0);
}
预期: 输出5000
结果如图:
这是因为线程是并发执行的,上一个线程对val进行++完,还没有写回内存,下个进程读取了之前的值对其++;
线程同步
1. 信号量
函数介绍
int sem_init(sem_t * sem, int pshared,unsigned int value)
sem: 指向的信号量对象
pshared: 0表示此信号为当前进程局部的,否则为多个进程间共享
value: 设置信号的值
int sem_wait(sem_t * sem)
进行p操作,信号值减1
sem: 指向的信号量对象
int sem_post(sem_t * sem)
进行v操作,信号值加1
sem: 指向的信号量对象
int sem_destory(sem_t * sem)
j清理该信号拥有的所有资源,成功返回0
sem: 指向的信号量对象
下面使用该方法对测试代码2修改
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>int val = 0;
sem_t sig;
void* fun(void* arg)
{for(int i=0;i<1000;i++){sem_wait(&sig);printf("%d\n",++val);sem_post(&sig);}return NULL;
}
int main()
{pthread_t id[5];sem_init(&sig,0,1);int i=0;for(;i<5;i++){pthread_create(&id[i],NULL,fun,(void*)&i);}for(i=0;i<5;i++){pthread_join(id[i],NULL);}sem_destroy(&sig);exit(0);
}
结果如图:
2.互斥锁
接口介绍:
1.int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t * mutexattr);
初始化锁
mutex: 互斥变量的指针
mutexattr: 设置互斥锁的属性
2.int pthread_mutex_lock(pthread_mutex_t* mutex);
上锁(加锁)
3.int pthread_mutex_unlock(pthread_mutex_t* mutex);
解锁
4.int pthread_mutex_destroy(pthread_mutex_t* mutex);
销毁锁
使用此方法修改测试代码2
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>int val = 0;
pthread_mutex_t mutex;
void* fun(void* arg)
{for(int i=0;i<1000;i++){pthread_mutex_lock(&mutex);printf("%d\n",++val);pthread_mutex_unlock(&mutex);}return NULL;
}int main()
{pthread_t id[5];pthread_mutex_init(&mutex,NULL);int i=0;for(;i<5;i++){pthread_create(&id[i],NULL,fun,(void*)&i);}for(i=0;i<5;i++){pthread_join(id[i],NULL);}pthread_mutex_destroy(&mutex);exit(0);}
结果如图: