线程初体验

线程的概念:线程是一个进程地址空间的一个控制流程,是调度的基本单位,由于同一进程的多个线程共享同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一 个全局变量,在各线程中都可以访问到。
各个线程共享的进程资源:

  • 文件描述符表
  • 每种信号的处理方式
  • 当前的工作目录
  • 用户id和组id
    各个线程各自拥有的资源:
  • 线程id
  • 上下文(各种寄存器的值,程序计数器,和栈指针)
  • 栈空间
  • errno变量
  • 信号屏蔽字
  • 调度优先级
    了解了线程的基本概念我们一起来看看程序中如何使用线程。

创建线程

线程创建函数

  • pthread_t *thread:输出型参数是新创建线程的线程id;
  • const pthread_attr_t *attr:线程的属性,目前我们只需要了线程创建的基本知识,属性方面不做深入的了解,所以这个参数可以设置为NULL,表示线程取缺省值。
  • void (*start_routine)(void ):函数指针指向一个参数为void*返回值为void*的函数。当调用pthread_create()创建新线程后当前线程从pthread_create()返回继续往下执行,而新线程执行的代码由函数指针start_routine决定start_routine函数接受一个参数是由pthread_create()通过参数arg传递的,这个参数的类型是void *按什么类型解释由用户自己决定。返回值也是同样,这个函数返回了这个新线程也就结束了。
  • void *arg:start_routine函数的参数。】

线程等待

父进程通过wait()函数得到子进程的退出状态,回收子进程的资源,那么如何获得线程的退出状态呢?接下来我们就来了解一下pthread_join()函数
线程等待函数

  • thread:线程id,由pthread_create()函数的第一个参数获得,新线程创建成功后线程id被存放到thread所指向的内存单元中,一个进程的pid类型是pid_t在整个系统中是唯一的,调用getpid()可以获得进程的pid,一个线程的id类型是pthread_t,只在当前进程中是唯一的,在不同的系统中实现方式不同,可以是整型的,也可以是结构体,不能直接当成一个整数来打印,可以通过pthread_self()来获得当前线程的id。
  • retval:通过这个参数获得新线程的返回值。

线程终止

如果需要终止某个线程而不是终止整个进程有以下三种方法,我们来使用这三种方法来终止线程并且了解一下pthread_join()函数的使用。

  • 从线程函数中返回,此方法不适用main函数,congmain函数中返回相当于调用exit(),终止整个进程。
#include<stdio.h>
#include<stdlib.h>
void *Fun()
{
printf("i am new thread pid: %d,tid :%lu\n", getpid(),pthread_self());
char *str = "yingying";
return ((void *)str); 
}
int main()
{
int id;
pthread_create(&id,NULL,Fun,NULL);
if(ret != 0)
{
char * str = strerror(ret);
printf("%s\n",str);
}
printf("i am root thread pid: %d,tid :%lu\n", getpid(),pthread_self());
void *val = NULL;
ret  = pthread_join(id,&val);
printf("The new thread has quited!\n val is %s\n",val);
sleep(1);
return 0;
}

这里写图片描述
由结果可见return终止了线程,pthread_join函数获得了Fun函数的返回值yingying。

  • 线程通过pthread_exit()终止自己
#include<stdio.h>
#include<stdlib.h>
void *Fun()
{
printf("i am new thread pid: %d,tid :%lu\n", getpid(),pthread_self());
pthread_exit((void*)223);
}
int main()
{
int id;
pthread_create(&id,NULL,Fun,NULL);
if(ret != 0)
{
char * str = strerror(ret);
printf("%s\n",str);
}
printf("i am root thread pid: %d,tid :%lu\n", getpid(),pthread_self());
void * val = NULL;
ret = pthread_join(id,&val);
printf("The new thread has quited!\n val is %d\n",(int)val);
sleep(1);
return 0;
}

这里写图片描述
由结果可见pthread_exit()终止了线程,val所指向的单元里存放着传递给pthread_exit()的参数

  • 一个线程调用pthread_cancel()函数来终止同一个进程中的另一个线程。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void *Fun()
{
while(1)
{
printf("i am new thread pid: %d,tid :%lu\n", getpid(),pthread_self());
sleep(1);
}
return NULL;
}
int main()
{
printf("i am root thread pid: %d,tid :%lu\n", getpid(),pthread_self());
int id;
int ret = pthread_create(&id,NULL,Fun,NULL);
if(ret != 0)
{
char * str = strerror(ret);
printf("%s\n",str);
}
void * val = NULL;
sleep(1);
pthread_cancel(id);
ret = pthread_join(id,&val);
printf("The new thread has quit!\n val is %d\n",(int)val);
return 0;
}

这里写图片描述
由结果可见pthread_cancel()终止了线程,val所指向的单元里存放着是常数PTHREAD_CANCELED。在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。
这里写图片描述

分离线程

默认的情况下线程是可结合的,为了避免存储泄露每个线程应该被显式的回收,即调用pthread_join(),或者调用pthread_detach()被分离,如果一个线程结束之后没有被join就类似于进程状态的僵死状态,即还有一部分资源未被回收。
如果调用了pthread_join(),但是线程没有运行结束,那么调用者就会进入阻塞状态,我们有时候并不希望如此;例如:web服务器中主线程为新的请求创建新线程后并不希望调用pthread_join()而阻塞,因为主线程还要处理其他的请求,此时可以在新线程的代码中加入:pthread_detach(pthread_self()),或者在主线程中加入pthread_detach(thread_id),
使线程被分离从而在运行结束时会自动释放其资源。
注意:不能对一个处于detach状态的线程调用pthread_join();对于一个尚未detach的线程调用pthread_join,pthread_detach,都可以使线程变为detach状态,我们不能对一个线程调用2次pthread_join(),所以一个线程调用了pthread_detach,就不能调用pthread_join()。

线程的同步与互斥

  • mutex:互斥量
    多个线程访问共享数据时可能会发生冲突,例如多个线程对全局变量进行加1,这个操作在一些平台需要三步操作:
    从内存中取变量到寄存器
    寄存器的值加1
    将寄存器里的值放回内存
    一个线程在执行以上任意一步时都有可能发生线程切换,导致加1操作未完成,由于当执行流由内核态切换到用户态时最容易触发进程切换,所以我们在两步之间加上一个printf语句,她会执行write系统调用进入内核,为线程切换提供了一个很好的机会,我们在一个循环中反复执行该操作,来看看结果是什么样的。
#include<stdio.h>
#include<pthread.h>
int gCount = 0;
void * Runfun(void *arg)
{
    int i = 0;
    int val;
    while(i < 5000)
    {
        val = gCount;
        printf("thread:%lu,%d\n",pthread_self(),gCount);
        gCount = val + 1;
        i++;
    }
    return NULL;
}
int main()
{
    pthread_t id1;
    pthread_t id2;
    pthread_create(&id1,NULL,Runfun,NULL);
    pthread_create(&id2,NULL,Runfun,NULL);
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    printf("%d\n",gCount);
    return 0;
}

这里写图片描述
这里写图片描述
由以上结果可以看出两个线程并没有将gCoubt加到10000,并且每次结果都不一样。
对于多线程访问冲突的情况我们引入了互斥锁,获得锁的线程可以完成“读-修改-写”的操作然后释放锁给其他线程,没有获得锁的线程只能等待,不能访问共享资源。这样的话“读-修改-写”的操作就是一个原子操作,要么做要么不做,不会被打断,也不会在其他处理器上并行操作。
这里写图片描述
函数的返回值同样是成功返回0失败返回错误号。我们可以用int pthread_mutex_lock(pthread_mutex_t*mutex);来创建锁采用pthread_mutex_init()函数来初始化,也可以采用是静态分配的mutex(全局变量或者static),并采用宏PTHREAD_MUTEX_INITIALIZER来初始化int pthread_mutex_unlock(pthread_mutex_t *mutex);函数是用来释放锁。

下面我们来对代码进行修改

#include<stdio.h>
#include<pthread.h>
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//创建锁并初始化
int gCount = 0;
void * Runfun(void *arg)
{
    pthread_mutex_lock(&lock);//对加1操作加锁
    int i = 0;
    int val;
    while(i < 5000)
    {
        val = gCount;
        printf("thread:%lu,%d\n",pthread_self(),gCount);
        gCount = val + 1;
        i++;
    }
    pthread_mutex_unlock(&lock);//释放锁
    return NULL;
}
int main()
{
    pthread_t id1;
    pthread_t id2;
    pthread_create(&id1,NULL,Runfun,NULL);
    pthread_create(&id2,NULL,Runfun,NULL);
    pthread_join(id1,NULL);
    pthread_join(id2,NULL);
    printf("%d\n",gCount);
    return 0;
}

下面让我们来看看你结果
这里写图片描述
由结果可以看出最终的结果是10000。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值