线程

线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程


对线程的操作:

因为线程不属于系统调用,因此需要用到pthread.h库

1.创建线程

#include <pthread.h>
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
//thread:线程id,返回型参数
//attr:线程属性,一般为NULL
//start_routine:所要执行的的函数(返回值void*,参数为void*)
//arg:函数的参数

2.终止线程

有三种方法可以终止一个线程:

  a.通过return返回,对主线程不适用.

  b.一个线程可以调用pthread_cancel终止其它线程.

  c.调用pthread_exit终止自己.

void pthread_exit (void*retval);
//retval:返回值,可以返回给pthread_join函数

3.线程等待

  根据线程的终止情况不同,pthread_join有以下三种情况:

  a.如果线程通过return返回,则retval中存放线程的返回值

  b.如果线程被异常终止,则retval中存放的是PTHREAD_CANCELED.

  c.如果线程通过pthread_exit终止,则retval中存放的是pthread_exit的参数.

int pthread_join(pthread_t pthread,void **retval);
//pthread:需要等待的线程id
//retval:带回的参数
//成功返回0,失败返回错误号

4.线程的分离与结合

  对于任意时间点,线程都是可分离的或者可结合的.

  可结合的线程可以被其他线程收回资源或者杀死,在其他线程回收资源之前,它的资源是不会被释放的.

  可分离的线程不能被其他线程回收或杀死,其资源是自动释放的.

  默认情况下,线程都是可结合的.

  如果一个可结合的线程运行完毕,没有被其他线程回收资源,那么它就会导致类似于僵尸进程的情况.

  当有线程调用pthread_join回收其它线程时,如果其他线程没有运行结束,则调用者会阻塞,为了避免这种情况,我们可以将该线程分离出去.

  分离线程需要调用:

int pthread_detach(thread_id);    //一般是分离自己pthread_detach(pthread_self);

  这样的话,就不必主线程去回收其资源,该线程运行完毕,会自动释放所有的资源.

eg:printtid.c:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>

void* thfu(void* arg) //线程函数
{
pid_t pid;
pthread_t tid;

pid=getpid();
tid=pthread_self();

printf("the thfu run:\npid:%d,tid:%d\n",pid,tid);
return NULL;
}


int main()
{
pid_t pid;
int err;
pthread_t tid;

pid=getpid();  //jin cheng id
tid=pthread_self();  //thread id
err=pthread_create(&tid,NULL,thfu,NULL);  //create thread

if(err!=0)
{
perror("pthread_create error");
exit(0);
}

sleep(1);  //parent sleep 1 second to make child run first
printf("the main run:\npid:%d,tid:%d\n",pid,tid);
exit(0);
}

    运行结果:

线程同步与互斥:

  • 互斥锁

  通过锁的机制实现线程间的互斥,同一时刻只有一个线程可以锁定它,当一个锁被某个线程锁定的时候,如果有另外一个线程尝试锁定这个临界区(互斥体),则第二个线程会被阻塞,或者说被置于等待状态。只有当第一个线程释放了对临界区的锁定,第二个线程才能从阻塞状态恢复运行。

  int pthread_mutex_init(pthread_mutex_t* mutex, const thread_mutexattr_t* mutexattr);初始化一个互斥锁。

  int pthread_mutex_lock(pthread_mutex_t* mutex);如果mutex被锁定,当前进程处于等待状态;否则,本进程获得互斥锁并进入临界区。

  int pthread_mutex_trylock(pthread_mutex_t* mutex);和lock不同的时候,尝试获得互斥锁不成功不会使得进程进入阻塞状态,而是继续返回线程执行。该函数可以有效避免循环等待锁,如果trylock失败可以释放已经占有的资源,这样可以避免死锁

  int pthread_mutex_unlock(pthread_mutex_t* mutex);释放互斥锁,并使得被阻塞的线程获得互斥锁并执行。

  int pthread_mutex_destroy(pthread_mutex_t* mutex);用来撤销互斥锁的资源。


pthread_mutex_t mutex;
pthread_mutex_init(&mutex,NULL);


void pthread1(void* arg){
   pthread_mutex_lock(&mutex);
   .....//临界区
   pthread_mutex_unlock(&mutex);
}


void pthread2(void* arg){
   pthread_mutex_lock(&mutex);
   .....//临界区
   pthread_mutex_unlock(&mutex);
}


eg:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//定义互斥变量,并初始化为快速互斥
int gn;

void* thread(void *arg)
{
    printf("thread's ID is  %d\n",pthread_self());
    pthread_mutex_lock(&mutex);
    gn = 12;
    printf("Now gn = %d\n",gn);
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main()
{
    pthread_t id;
    printf("main thread's ID is %d\n",pthread_self());
    gn = 3;
    printf("In main func, gn = %d\n",gn);
    if (!pthread_create(&id, NULL, thread, NULL)) {
        printf("Create thread success!\n");
    } else {
        printf("Create thread failed!\n");
    }
    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

  运行结果:


  • 读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。适用于读的次数大于写的次数的数据结构。

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

读锁锁住,加读锁,可以;加写锁会被阻塞,但此时会阻塞后续的读锁请求,防止读锁长期占用无法进入写模式。写锁就是互斥锁。

int pthread_rwlock_init(pthread_rwlock_t* rwlock, const pthread_rwlockattr_t* attr);初始化读写锁

int pthread_destroy(pthread_rwlock_t* rwlock);销毁读写锁

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);加读锁

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);加写锁

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);解锁


eg:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

int gn;
pthread_rwlock_t q_rwlock;

void* thread(void *arg)
{
    printf("thread's ID is  %d\n",pthread_self());
    pthread_rwlock_tryrdlock(&q_rwlock);
    gn = 12;
    printf("Now gn = %d\n",gn);
    pthread_rwlock_unlock(&q_rwlock);
    return NULL;
}

int main()
{
    pthread_t id;
    printf("main thread's ID is %d\n",pthread_self());
    gn = 3;
    printf("In main func, gn = %d\n",gn);
    if (!pthread_create(&id, NULL, thread, NULL)) {
        printf("Create thread success!\n");
    } else {
        printf("Create thread failed!\n");
    }
    pthread_join(id, NULL);
    pthread_rwlock_destroy(&q_rwlock);
    return 0;
}
运行结果同上
  • 条件变量

  信号量只有锁住和不锁两种状态,而且当条件变量和信号量一起使用时,允许线程以无竞争的方式等待特定的条件发生

  条件本身是由互斥量保护的:线程在改变条件状态之前必须先锁住互斥量。

  int pthread_cond_init(pthread_cond_t* cond,const pthread_condattr_t* attr);初始化动态分配的条件变量;也可以直接用PTHREAD_INITIALIZER直接赋值给静态的条件变量

  int pthread_cond_destroy(pthread_cond_t* cond)撤销条件变量资源;

  int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);使用该函数使得等待条件变量为真,线程被条件变量cond阻塞。

  int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex,const struct timespec* tspr);与wait类似,只是经历tspr时间后,即使条件变量不满足,阻塞也被解除,返回错误码。确定到达什么时间要判定为超时

  int pthread_cond_signal(pthread_cond_t* cond);唤醒因为条件变量阻塞的线程。

  int pthread_cond_broadcast(pthread_cond_t* cond);唤醒等待该条件的所有线程。

pthread_cond_t cond;
pthread_mutex_t mutex;
int count=0;
void pthread1(void* arg){
    pthread_mutex_lock(&mutex);
    while(count==0)
        pthread_cond_wait(&cond,&mutex);
    count--;
    pthread_mutex_unlock(&mutex);
}
void pthread2(void* arg){
    pthread_mutex_lock(&mutex);
    if(count==0)
        pthread_cond_signal(&cond);
    count++;
    pthread_mutex_unlock(&mutex);
}

eg:生产者消费者:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<pthread.h>


pthread_mutex_t cond_mutex=PTHREAD_MUTEX_INITIALIZER;//互斥变量
pthread_cond_t condition=PTHREAD_COND_INITIALIZER;//条件变量

int workcount=0;

#define MAX_CONSUMERS 10

void* producerThread(void* arg)
{
    int i,j,ret;
    double result=0.0;

    printf("producer run\n");

    for(i=0;i<30;i++)
    {   
        ret=pthread_mutex_lock(&cond_mutex);
        if(ret==0)
        {   
            printf("produer creating work (%d)\n",workcount);
            workcount++;
            pthread_cond_broadcast(&condition);
            pthread_mutex_unlock(&cond_mutex);
        }   

        for(j=0;j<3000;j++)
        {   
            result+=(double)random();
        }   
    }   
    printf("producer finished\n");
    pthread_exit(NULL);
}



void* consumerThread(void* arg)
{
    int ret;
    pthread_detach(pthread_self());
    printf("consumer %x run\n",pthread_self());

    while(1)
    {   
        assert((pthread_mutex_lock(&cond_mutex))==0);
        ret=pthread_cond_wait(&condition,&cond_mutex);
        assert(ret==0);

        if(workcount)
        {   
            workcount--;
            printf("consumer %x:do work(%d)\n",pthread_self(),workcount);
        }   
    
        assert((pthread_mutex_unlock(&cond_mutex))==0);
    }   
    
    printf("consumer %x:finished\n",pthread_self());
    pthread_exit(NULL);
}



int main()
{
    int i;
    pthread_t consumers[MAX_CONSUMERS]={0};
    pthread_t producer;

    for(i=0;i<MAX_CONSUMERS;i++)
    {//创建消费者线程
        pthread_create(&consumers[i],NULL,consumerThread,NULL);
    }   
    //创建消费者线程
    pthread_create(&producer,NULL,producerThread,NULL);
    pthread_join(producer,NULL);//挂起生产者线程

    while(workcount>0);//等待工作完成

    for(i=0;i<MAX_CONSUMERS;i++)
    {   
        pthread_cancel(consumers[i]);
    }   

    pthread_mutex_destroy(&cond_mutex);
    pthread_cond_destroy(&condition);

    return 0;
}

运行结果:


  • 自旋锁

  互斥量阻塞线程的方式是使其进入睡眠,而自旋锁是让线程忙等,即不会使其睡眠,而是不断循判断自旋锁已经被解锁。

  适用于占用自旋锁时间比较短的情况。

  • 信号量

介绍一下POSIX(POSIX标准定义了操作系统应该为应用程序提供的接口标准,换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。)的信号量机制,定义在头文件/usr/include/semaphore.h

1)初始化一个信号量:sem_init()

int sem_init(sem_t* sem,int pshared,unsigned int value);

pshared为0时表示该信号量只能在当前进程的线程间共享,否则可以进程间共享,value给出了信号量的初始值。

2)阻塞线程

sem_wait(sem_t* sem)直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少;sem_trywait(sem_t* sem)是wait的非阻塞版本,它直接将sem的值减一,相当于P操作。

3)增加信号量的值,唤醒线程

sem_post(sem_t* sem)会使已经被阻塞的线程其中的一个线程不再阻塞,选择机制同样是由线程的调度策略决定的。相当于V操作。

3)释放信号量资源

sem_destroy(sem_t* sem)用来释放信号量sem所占有的资源

pthread_mutex_t mutex;
sem_t full,empty;

void producer(void* arg){
    while(1){
    sem_wait(&empty);//need to produce. the the empty of resource need minus 1
    pthread_mutex_lock(&mutex);
    ...//produce a resource
    pthread_mutex_unlock(&mutex);
    sem_post(&full); //have produced a resource, the the full of resource need add 1
    }
}
void consumer(void* arg){
    while(1){
    sem_wait(&full);
    pthread_mutex_lock(&mutex);
    ...//consume a resource
    pthread_mutex_unlock(&mutex);
    sem_post(&empty); 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值