1、线程
每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。为了减少系统开销,从进程中演化出了线程。
线程存在于进程中,共享进程的资源。
线程是进程中的独立控制流,由环境(包括寄存器组和程序计数器)和一系列的执行指令组成。
每个进程有一个地址空间和一个控制线程
2、进程VS线程
进程是系统中程序执行和资源分配的基本单位。线程是CPU调度和分派的基本单位。通俗来讲进程类似公司的一个部门,线程就是部门中的每个小组。
线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但它可以去访问其所属进程的资源,如进程代码段,数据段以及系统资源(已打开的文件,I/O设备等)。
同一个进程中的多个线程可共享同一地址空间,因此它们之间的同步和通信的实现也变得比较容易。
在进程切换时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和提高系统的吞吐量。
不仅进程间可以并发执行,而且在一个进程中的多个线程之间也可以并发执行。
3、多线程使用
一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
为提高网络的利用效率,我们可以使用多线程,对每个连接用一个线程去处理。
同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。
注意:
默认情况下,一个进程当中只有一个线程,叫做主控线程,由主控线程创建的线程
称之为子线程,不管是主控线程还是子线程,都附属于当期的进程
如果关闭当前进程,那么当前进程中的所有的线程都会自动退出
线程退出时不会影响当前所在的进程的执行的
4、线程创建和使用
4.1、pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个新的子线程
参数:
thread:当前创建的线程id
attr:线程的属性,设置为NULL表示以默认的属性创建
start_routine:线程处理函数,如果当前函数执行完毕,
则子线程也执行完毕
arg:给线程处理函数传参用的
返回值:
成功:0
失败:非0
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//由于线程相关的函数原本不是系统中的函数,所以使用是需要手动链接库文件 -lpthread
//创建的子线程间也没有先后运行顺序
void *pthread_fun1(void *arg)
{
printf("子线程1正在运行\n");
}
void *pthread_fun2(void *arg)
{
printf("子线程2正在运行\n");
}
int main(int argc, char const *argv[])
{
printf("主控线程正在运行\n");
//使用pthread_create创建一个子线程
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
while(1)
;
return 0;
}
线程有点类似函数,可以直接使用全局变量,用法与函数类似。
每一个子线程拥有独立栈区、堆区、静态区.
4.2、pthread_self()
#include <pthread.h>
pthread_t pthread_self(void);
功能:获取当前线程的id
参数:无
返回值:
成功:当前线程的id
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *pthread_fun1(void *arg)
{
printf("子线程1:%#lx, 正在运行\n", pthread_self());
}
void *pthread_fun2(void *arg)
{
printf("子线程2:%#lx, 正在运行\n", pthread_self());
}
int main(int argc, char const *argv[])
{
printf("主控线程:%#lx, 正在运行\n", pthread_self());
//使用pthread_create创建一个子线程
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
printf("thread1 = %#lx\n", thread1);
printf("thread2 = %#lx\n", thread2);
while(1)
;
return 0;
}
4.3、pthread_exit()
#include <pthread.h>
void pthread_exit(void *retval);
功能:退出正在执行的线程
参数:
retval:当前线程的退出状态值,
这个值可以被调用pthread_join函数的线程接收到
返回值:无
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun1(void *arg)
{
while(1)
{
printf("子线程1:%#lx, 正在运行\n", pthread_self());
sleep(1);
}
}
void *pthread_fun2(void *arg)
{
int num = 0;
while(1)
{
printf("子线程2:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 5)
{
pthread_exit(NULL);
}
}
}
int main(int argc, char const *argv[])
{
printf("主控线程:%#lx, 正在运行\n", pthread_self());
//使用pthread_create创建一个子线程
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
printf("thread1 = %#lx\n", thread1);
printf("thread2 = %#lx\n", thread2);
//如果主控线程关闭,进程还没有关闭,所以子线程可以接着执行
//pthread_exit(NULL);
while(1)
;
return 0;
}
4.4、pthread_join()
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待一个子线程的退出,
可以接收到某一个子线程调用pthread_exit时设置的退出状态值
参数:
thread:指定线程的id
retval:保存子线程的退出状态值,如果不接受则设置为NULL
返回值:
成功:0
失败:非0
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun1(void *arg)
{
int num = 0;
static char buf[] = "The thread1 has quited\n";
while(1)
{
printf("子线程1:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 3)
{
pthread_exit(buf);
}
}
}
void *pthread_fun2(void *arg)
{
int num = 0;
static int value = 999;
while(1)
{
printf("子线程2:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 5)
{
pthread_exit(&value);
}
}
}
int main(int argc, char const *argv[])
{
printf("主控线程:%#lx, 正在运行\n", pthread_self());
//使用pthread_create创建一个子线程
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
printf("thread1 = %#lx\n", thread1);
printf("thread2 = %#lx\n", thread2);
char *s;
int *v;
if(pthread_join(thread1, (void **)&s) != 0)
{
perror("fail to pthread_join");
}
printf("s = %s\n", s);
if(pthread_join(thread2, (void **)&v) != 0)
{
perror("fail to pthread_join");
}
printf("*v = %d\n", *v);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
4.5、pthread_cancel()
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:在一个线程中销毁另一个线程
参数:
thread:要销毁的线程的id
返回值:
成功:0
失败:非0
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_t thread1, thread2;
void *pthread_fun1(void *arg)
{
while(1)
{
printf("子线程1:%#lx, 正在运行\n", pthread_self());
sleep(1);
}
}
void *pthread_fun2(void *arg)
{
int num = 0;
static int value = 999;
while(1)
{
printf("子线程2:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 5)
{
pthread_cancel(thread1);
pthread_exit(&value);
}
}
}
int main(int argc, char const *argv[])
{
printf("主控线程:%#lx, 正在运行\n", pthread_self());
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
printf("thread1 = %#lx\n", thread1);
printf("thread2 = %#lx\n", thread2);
//sleep(3);
//pthread_cancel(thread1);
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
4.6 、 pthread_detach()
Linux中线程分为两种状态:可结合的(joinable)或分离的(detached),线程默认创建为结合态
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是detached状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放,使用pthread_detach函数将线程设置为分离态。
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:将一个子线程设置为分离态
参数:
thread:指定的子线程的id
返回值:
成功:0
失败:非0
example:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
void *pthread_fun1(void *arg)
{
//pthread_detach(pthread_self());
int num = 0;
static char buf[] = "The thread1 has quited\n";
while(1)
{
printf("子线程1:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 3)
{
pthread_exit(buf);
}
}
}
void *pthread_fun2(void *arg)
{
int num = 0;
static int value = 999;
while(1)
{
printf("子线程2:%#lx, 正在运行\n", pthread_self());
sleep(1);
num++;
if(num == 5)
{
pthread_exit(&value);
}
}
}
int main(int argc, char const *argv[])
{
printf("主控线程:%#lx, 正在运行\n", pthread_self());
//使用pthread_create创建一个子线程
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
}
printf("thread1 = %#lx\n", thread1);
printf("thread2 = %#lx\n", thread2);
//默认创建的子线程是结合态,当线程退出时不会释放堆栈的资源,所以要使用pthread_join
//但是pthread_join函数是一个阻塞函数,所以会影响原本主控线程的执行,所以要设置线程的
//状态为分离态
#if 0
char *s;
int *v;
if(pthread_join(thread1, (void **)&s) != 0)
{
perror("fail to pthread_join");
}
printf("s = %s\n", s);
if(pthread_join(thread2, (void **)&v) != 0)
{
perror("fail to pthread_join");
}
printf("*v = %d\n", *v);
#endif
//使用pthread_detach函数设置线程的状态为分离态
if(pthread_detach(thread1) != 0)
{
perror("fail to pthread_detach");
exit(1);
}
if(pthread_detach(thread2) != 0)
{
perror("fail to pthread_detach");
exit(1);
}
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
4.7、其他函数
pthread_setcancelstate()
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
功能:设置当前子线程是否能够被其他线程销毁(取消)
参数:
state:要设置的状态
PTHREAD_CANCEL_ENABLE 可以被取消
PTHREAD_CANCEL_DISABLE 不可被取消
oldstae:之前的状态
返回值
pthread_setcanceltype()
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
功能:设置当前线程是否被立即取消
参数:
type:设置的状态
PTHREAD_CANCEL_DEFERRED 不立即被取消
PTHREAD_CANCEL_ASYNCHRONOUS 立即取消
oldtype:之前的状态
返回值:
成功:0
失败:非0
5、线程间同步与互斥
在多任务操作系统中,同时运行的多个任务可能都需要访问/使用同一种资源,多个任务之间有依赖关系,某个任务的运行依赖于另一个任务,同步和互斥就是用于解决这两个问题的。
互斥:
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。POSIX标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式。
同步:
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行,同步是特殊的互斥。
5.1互斥的使用
pthread_mutex_init()
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化一个互斥锁
参数:
mutex:指定的互斥锁
mutexattr:互斥锁的属性,为NULL表示默认属性
返回值:
成功:0
pthread_mutex_lock()
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:上锁操作
参数:
mutex:指定的互斥锁
返回值:
成功:0
失败:非0
pthread_mutex_unlock()
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁操作
参数:
mutex:指定的互斥锁
返回值:
成功:0
失败:非0
pthread_mutex_destroy()
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一个互斥锁
参数:
mutex:指定的互斥锁
返回值:
成功:0
失败:非0
创建,销毁,加锁,解锁,在应用中都是成对使用。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//使用互斥锁解决线程间互斥问题
//注意:互斥锁是给共享资源的操作进行上锁和解锁,而不是对共享资源
//第一步:创建一个互斥锁(由于两个线程用的是同一个锁,所以要放在全局)
pthread_mutex_t mutex;
int money = 10000;
void *pthread_fun1(void *arg)
{
int get, yu, shiji;
get = 10000;
//第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mutex);
printf("张三正在查询余额...\n");
sleep(1);
yu = money;
printf("张三正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("张三想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
//第四步:操作完毕后,进行解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
int get, yu, shiji;
get = 10000;
//第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mutex);
printf("李四正在查询余额...\n");
sleep(1);
yu = money;
printf("李四正在取钱...\n");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("李四想取%d元,实际取了%d元,余额为%d元\n", get, shiji, yu);
//第四步:操作完毕后,进行解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
//第二步:初始化互斥锁
pthread_mutex_init(&mutex, NULL);
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//第五步:销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
5.2、条件变量
条件变量一般与互斥锁一起使用,解决同步问题,条件变量一般有发送信号通知和接收信号通知两个步骤,如果没有发送信号通知,则接受信号通知的线程会一直阻塞,所以让后执行的线程接收信号通知,让先执行的线程执行完毕后发送信号通知.
pthread_cond_init()
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr)
功能:初始化一个条件变量
参数:
cond:指定的条件变量
attr:条件变量的属性,为NULL表示默认属性
返回值:
成功:0
失败:非0
pthread_cond_destroy()
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量
参数:
cond:指定的条件变量
返回值:
成功:0
失败:非0
pthread_cond_wait()
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:阻塞等待接收一个信号通知
参数:
cond:指定的条件变量
mutex:指定的锁
返回值:
成功:0
失败:非0
pthread_cond_signal()
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
功能:
第一个可以发送给所有的线程(所有的线程都可以接收到),
第二个函数只能发送给一个线程
参数:
cond:指定的条件变量
返回值:
成功:0
失败:非0
example:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex;
//使用条件变量解决同步问题
//第一步:创建一个条件变量
pthread_cond_t cond;
int n = 0;
void *pthread_fun1(void *arg)
{
printf("正在做蛋糕...\n");
sleep(1);
printf("蛋糕做好了\n");
n = 5;
//第四步:先执行的线程执行完毕后给后执行的线程发送信号通知
//pthread_cond_signal(&cond);
//pthread_cond_signal(&cond);
//pthread_cond_signal(&cond);
//pthread_cond_signal(&cond);
//pthread_cond_signal(&cond);
pthread_cond_broadcast(&cond);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
pthread_mutex_lock(&mutex);
//第三步:后执行的线程阻塞等待接收信号通知
//pthread_cond_wait函数的机制
//第一步:解锁
//第二步:上锁
//第三步:将当前要接收信号通知的线程加入到消息队列中
//第四步:解锁
//第五步:阻塞等待接收信号通知
//第六步:如果先执行的线程执行完毕后发送了信号通知,
// 上锁,然后将消息队列中等待的线程从消息队列中移除
//第七步:解锁
pthread_cond_wait(&cond, &mutex);
if(n > 0)
{
printf("正在买蛋糕...\n");
sleep(1);
printf("%#lx蛋糕买完了\n", pthread_self());
n--;
}
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
pthread_mutex_init(&mutex, NULL);
//第二步:初始化一个条件变量
pthread_cond_init(&cond, NULL);
pthread_t thread1, thread2, thread3, thread4, thread5, thread6;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread3, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread4, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread5, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread6, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
//第五步:销毁条件变量
pthread_cond_destroy(&cond);
return 0;
}
5.3、信号量
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞。
信号量又称之为PV操作
P操作:减操作
V操作:加操作
信号量是一个计数器,可以设置一个初始值,必须是一个非负整数,P操作执行一次,则数值减一,V操作执行一次数值加一,如果数值变为0,则P操作无法执行,会一直阻塞,但是V操作没有影响
相关函数:
sem_init()
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个信号量
参数:
sem:指定的信号量
pshared:是否在线程间或者进程间共享
0 线程间共享
1 进程间共享
value:信号量的初始值
返回值:
成功:0
失败:-1
sem_destroy()
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁一个信号量
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
sem_wait()
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:执行P操作,执行一次,信号量的值减1,如果为0,则阻塞
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
sem_post()
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:执行V操作,执行一次,信号量的值加1
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
example:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
//第一步:创建一个信号量
sem_t sem;
void *pthread_fun1(void *arg)
{
printf("正在做蛋糕...\n");
sleep(1);
printf("蛋糕做好了\n");
//第四步:先执行的线程执行完毕后执行V操作
sem_post(&sem);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
//第三步:让后执行的线程执行P操作,阻塞等待先执行的线程执行完毕后执行V操作后函数立即返回
sem_wait(&sem);
printf("正在买蛋糕...\n");
sleep(1);
printf("蛋糕买完了\n");
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
//第二步:初始化一个信号量
sem_init(&sem, 0, 0);
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//第五步:销毁信号量
sem_destroy(&sem);
return 0;
}