前言

之前讨论了进程,了解一个进程能做一件事情,如果想同时处理多件事情,那么需要多个进程,但是进程间很不方便的一点是,进程间的数据交换似乎没有那么方便。Linux提供线程功能,能在一个进程中,处理多任务,而且线程之间的数据是完全共享的。

线程也有PCB,它的PCB和进程的PCB结构完全一样,只是它里面保存的虚拟地址空间和创建它的进程的虚拟地址空间完全保持一致。

摘要

线程的创建

通过pthread_create函数可以创建一个线程,被创建的线程的例程,就是一个新的执行指令序列了。

#include <pthread.h>

void* thread_func(void* p )
{
    return NULL;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%dn", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%dn", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%dn", (int)tid);
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("tid=%dn", (int)tid);

    getchar();
}

 

 

Compile and link with -lpthread.

 

补充
intptr_t是一种整型,它的长度依赖机器位长,也就意味着它的长度和指针的长度一样的。

线程概念,线程与进程的区别与联系
学会线程控制,线程创建,线程终止,线程等待
了解线程分离与线程安全
学会线程同步
学会使用互斥量,条件变量,posix信号量,读写锁

 线程标识

线程使用pthread_t来标识线程,它也是一个非负整数,由系统分配,保证在进程范围内唯一。pthread_t虽然在Linux下是非负整数,但是在其它平台下不一定是,所以比较线程号是否想等,应该用pthread_equal

任何一个函数都可以调用pthread_self来获取目前代码运行的线程。

线程概念

main函数和信号处理函数是同一个进程地址空间中的多个控制流程,多线程也是如此.
信号处理函数的控制流程指示在信号递达时产生,在处理完信号之后结束.而多线程的控制流程可以长期并存,操作系统在各个线程之间调度和切换.
同一进程的多个线程共享同一地址空间,因此,代码段,数据段都是共享的,只有栈是私有的.

同一进程的线程共享资源:
代码段
数据段
文件描述符表
每种信号的处理方式或者自定义函数
当前工作目录
用户id和组id

各有一份:
线程id
上下文,包括各种寄存器值,程序计数器和栈指针
errno变量
信号屏蔽字
调度优先级

编译时加选项-lpthread

线程终止

终止方式  
例程返回 正常退出
调用pthread_exit 正常退出
响应pthread_cancel 异常退出

注意:

  • 在线程里调用exit是退出整个进程。

  • 在多线程的进程中,主线程调用pthread_exit,进程并不会退出,它的其他线程依旧在执行,但是主线程已经退出了。

  • 意味着:主线程和其他线程是几乎是平等的。

  • 不平等的是,如果主线程的main函数return了,那么其他线程也结束了,如果其他线程的入口函数return了,主线程不会跟着结束。

线程控制

A创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t*
attr,void*(*start_routine)(void*),void* arg);
当start_routine返回时,这个线程退出,其他线程可以调用pthread_join得到start_routine的返回值
pthread_self可以得到当前线程的线程id.

如果任意一个线程调用了exit或者_exit,则整个进程的所有线程都终止,或者从main函数return,所有线程也终止

B线程终止
如果需要只终止某个线程而不终止整个进程
1从线程函数return,(不包括主线程)
2一个线程可以调用pthread_cancel终止同一进程中的另一个线程
3线程调用pthread_exit终止自己

注:线程中返回的指针应当是指向全局的或者malloc获取的,因为线程的栈是私有的

C.线程等待
int pthread_join(pthread_t thread,void* * retval);
返回值:成功返回0,失败返回错误号

调用该函数的线程将挂起等待,直到id为thread的线程终止.
不同终止方式,pthread_join得到的终止状态是不同的:
1return返回,retval指向的单元存放返回值
2被别的线程调用pthread_cancel异常终止,存放常数PTHREAD_CANCELED
3自己调用pthread_exit终止,存放传给pthread_exit的参数.
如果对返回值不感兴趣,传NULL给retval

线程除了可以终止后等待pthread_join接收之外,还可以设置为detach状态
这样的线程一旦终止就收回它占用的所有资源,而不保留终止状态.
对一个线程调用pthread_join或pthread_detach都可以把线程设置为detach状态,所以不能对一个线程同时使用两个

线程的回收

线程退出之后,它的PCB依旧在内核中存在,等着其它线程来获取它的运行结果,可以通过pthread_join来回收线程。从这个角度看,线程和进程差不多,但是跟进程不同的时,线程没有父线程的概念,同一个进程内的其它线程都可以来回收它的运行结果。

pthread_join会阻塞调用它的线程,一直到被join的线程结束为止。

pthread_joinwait/waitpid一样,也是阻塞的调用,它除了有回收PCB的功能,也有等待线程结束的功能。

线程分离

线程是可结合的(joinable)或者是可分离的(detached)

结合的线程能被其他线程收回资源和杀死.在被收回之前,他的存储器资源是不释放的.
分离线程则是不能被其他线程收回或者杀死的,他的存储器资源在终止时由系统自动释放

默认情况,线程是joinable状态.如果一个线程没有被join而结束了,那么他就是类似进程中的僵尸状态.

在主线程需要非阻塞方式时,可以在字线程中使用
pthread_detach(pthread_self())
或者在父线程中使用pthread_detach(thread_id)
进行线程分离.如此,主线程不阻塞,同时字线程资源自动释放

线程的使用场景

线程同步与互斥

A.mutex(互斥量)
int pthread_mutex_init(pthread_mutex_t * restrict mutex, const
const pthread_mutexattr_t * restrict attr);
int pthread_mutex_destroy(pthread_mutext_t * mutex);
pthread_mutext_t mutex = PTHREAD_MUTEX_INITIALIZED;

参数attr设定metex的树形,如果为NULL缺省
如果mutex变量是静态分配的(全局变量或者static变量)可以使用宏定义PTHREAD_MUTEX_INITIALIZED初始化

加锁解锁操作
int pthread_mutex_lock(pthread_mutext_t*mutex);
int pthread_mutex_trylock(pthread_mutext_t*mutex);
int pthread_mutex_unlock(pthread_mutext_t*mutex);

如果一个锁机箱获得锁,又想不挂起,调用pthread_mutex_trylock,如果被占用,那么失败返回EBUSY,而不挂起等待

 客户端使用场景

一般来说,线程用于比较复杂的多任务场景,比如:

 www.512.net 1

这样主线程可以基础处理主线程的事情,不至于被复杂的任务阻塞。比如:

 

www.512.net 2

这样聊天界面不会卡死在那里,否则如果网络情况很差,有可能导致界面卡死。

死锁

如果一个线程先后调用两次lock,第二次时,由于占用挂起.然而锁自己用着,挂起没机会释放,所以就永久等待.这就是死锁
另一种死锁,两个线程使用了对方需求的锁,而又申请对方已经占用的锁.

在写程序时,应当避免同时获取多个锁,如果有必要那么:
如果所有线程需要多个锁,都按相同的顺序获取锁,则不会出现死锁

B.Condition varialbe(条件变量)

一个例子:
生产者5秒生产一个资源,消费者2秒消费一个产品,使用mutex保护处理时.那么,消费者会有每次都会有三秒的空探索.
这时我们可以改进程序.
除了锁的问题,我们条件控制,申请锁,看条件是否成立,如果成立,那么消费,否则,释放锁.阻塞等待.
当消费者产生条件时通知,我就重新获取锁并消费

int pthread_cond_destroy(pthread_cond_t * cond);
int pthread_cond_init(pthread_cond_t *restrict cont, const
pthread_condattr_t * restrict attr);
pthread_cond_t cont = PTHREAD_COND_INITIALIZED;

int pthread_cond_broadcast(pthread_cond_t*cond);
//广播通知条件成熟
int pthread_cond_signal(pthread_cond_t *cond);//通知条件成熟
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t*
lock);
int pthread_cond_timewait(pthread_cond_t
*cond,pthread_mutex_t* lock,time_value* timeout);

一个condition
varialbe总是和一个mutex搭配使用的.一个线程可以调用pthread_cond_wait在一个vondtion
variable上阻塞等待.该函数做以下三步骤:

1释放mutex
2阻塞等待
3当被唤醒时,重型获得mutex并返回

服务器使用场景

服务器一般的流程如下:

 www.512.net 3

在服务器上,一个线程来处理整个流程,会导致处理流程非常慢,导致主线程无法及时接收报文。一般会使用子线程来做具体的工作,而主线程只负责接收报文。

www.512.net 4

有时为了提高处理效率,会使用线程池

 

 

C.semaphore信号量

mutex变量是非0即1的,可以看作哦可用资源的可用数量,初始为1.
semaphore变量类型为sem_t
int sem_init(sem_t*sem,int pshared,unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t*sem);
int sem_post(sem_t*sem);
int sem_destroy(sem_t *sem);

调用sem_wait获得资源
调用sem_post可以使放资源

7.7 线程的同步

无论上述那种场景,都有一个报文队列或者消息队列,一般这个队列是一个链表,主线程需要往链表中添加数据,而子线程从链表获取数据。两个线程同时操作一个全局变量是不安全的,应该避免不安全的访问。无论这种全局变量是数组、链表、还是一个简单的变量。

线程A:i = i + 1;
线程B:i = i + 1;

D.读写锁

多读少写的代码加锁
读写锁实际是一种特殊的自旋锁,他把对共享资源的访问划分为读者和写着,读者制度,写着进行写操作.
这种锁相对于自旋锁而言,能提高并发性,最大可能的读者是实际逻辑CPU数.
写者是排他性的,一个读写锁智能有一个写者或者多个读者
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const
pthread_rwlockattr_t * restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);

7.7.1 不安全的案例

  • 多线程操作一个全局变量

#include <stdio.h>

#include <signal.h>

#include <pthread.h>

 

int result=0;

 

void add()

{

    int i;

    for(i=0; i<100000; ++i)

    {

        result++;

    }

}

 

void* thread_func(void* p)

{

    add();

    return NULL;

}

 

int main()

{

www.512.net,    pthread_t t1;

    pthread_t t2;

 

    pthread_create(&t1, NULL, thread_func, NULL);

    pthread_create(&t2, NULL, thread_func, NULL);

 

    pthread_join(t1, NULL);

    pthread_join(t2, NULL);

 

    printf(“%dn”, result);

    return 0;

}

  • 不安全的生产者消费者模型

#include <list>

 

struct task_t

{

    int task;

};

 

list<task_t*> queue;

 

void* work_thread(void* arg)

{

    while(1)

    {

        if(queue.size() == 0) continue;

 

        task_t* task = *queue.begin();

        queue.pop_front();

 

        printf(“task value is %dn”, task->task);

        delete task;

    }

}

 

void main(int argc, char* argv[])

{

    pthread_t tid;

    pthread_create(&tid, NULL, work_thread, NULL);

 

    while(1)

    {

        int i;

        cin >> i;

        task_t* task = new task_t;

        task->task = i;

 

        queue.push_back(task);

    }

 

    pthread_join(tid, NULL);

}

linux下的锁

自旋锁,文件锁,大内核锁…
自旋锁:busy-waiting
互斥锁:sleep-waiting

因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远
高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
   
 1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时
间内获得锁,这无疑会使CPU效率降低。
   
 2.在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如
copy_to_user()、copy_from_user()、kmalloc()等。
因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

文件锁

防止两个进程同时操作文件而相互影响的问题

文件锁:
协同锁
如果一个进程申请文件锁并访问文件,另一个进程可以访问文件,但是被认为是非法的;
如果后进进程试图申请文件锁,那么就会申请失败,所以就协同工作了
强制锁
强制文件必须通过申请锁资源才能进行访问

7.7.2 锁(临界量)

锁能避免两个线程同时访问一个全局变量。
锁会带来两个问题:

  • 效率低

  • 死锁

    #include <stdio.h>
    #include <pthread.h>
    
    int result = 0;
    // 定义锁,锁一般也定义在全局
    //pthread_mutex_t mutex;  // 粗粒度的锁
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
    int result1 = 0;
    pthread_mutex_t mutex1;
    
    // 1.一个线程重复加锁两次,会死锁
    void func()
    {
        pthread_mutex_lock(&mutex);
    
        pthread_mutex_unlock(&mutex);
    }
    
    void foo()
    {
        pthread_mutex_lock(&mutex);
        func();
        pthread_mutex_unlock(&mutex);
    }
    
    // 2. 一个线程加锁之后,忘记了解锁
    void foo1()
    {
    
        pthread_mutex_lock(&mutex);
        if(...) // 这种场合容易产生忘记解锁
            return;
        // ....
        // 忘记了解锁
        pthread_mutex_unlock(&mutex);
    }
    
    void foo2()
    {
        // 因为别的线程忘记解锁,所以本线程无法进行加锁
        pthread_mutex_lock(&mutex); // 阻塞在这里
        pthread_mutex_unlock(&mutex);
    }
    
    void* thread_func(void* ptr)
    {
        foo();
    
        int i=0;
        for(i=0; i<100000; ++i)
        {
            pthread_mutex_lock(&mutex1);
            result1++;//它的值由什么决定
            pthread_mutex_unlock(&mutex1);
    
            // 两个线程同时操作全局变量,结果不可靠
            //
            // 将该操作变成原子操作,或者至少不应该被能影响它操作的人打断
            pthread_mutex_lock(&mutex);
            result ++;  // result++代码被锁保护了,不会被其他线程的result++影响
            pthread_mutex_unlock(&mutex);
        }
        return NULL;
    }
    
    int main()
    {
        // 使用锁之前,要对它进行初始化
    //    pthread_mutex_init(&mutex, NULL);
        pthread_mutex_init(&mutex1, NULL);
    
        pthread_t t1, t2;
        pthread_create(&t1, NULL, thread_func, NULL);
        pthread_create(&t2, NULL, thread_func, NULL);
    
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
    
        printf("result is %dn", result);
    }
    
    #include <stdio.h>
    #include <list>
    #include <iostream>
    using namespace std;
    
    struct task_t
    {
        int task;
    };
    
    // 全局的任务队列
    list<task_t*> tasks;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    
    // pthred_cond_signal和pthread_cond_wait类似不可靠信号,signal不会累计
    // 当一个线程发送signal时,如果另外一个线程此时没有调用wait函数,那么这个signal就会消失掉

    void* work_thread(void* ptr)
    {
        while(1)
        {
            // 等待条件
            pthread_mutex_lock(&mutex);
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);

            // 一旦条件满足,就应该处理队列中所有的任务
            while(1)
            {
                pthread_mutex_lock(&mutex);
                if(tasks.size() == 0) 
                {
                    pthread_mutex_unlock(&mutex); // 特别容易忘记解锁
                    break;
                }
                task_t* task = *tasks.begin();
                tasks.pop_front();
                pthread_mutex_unlock(&mutex);

                // 处理任务
                printf("current task is %dn", task->task);

                // new和delete(malloc和free)都是线程安全的
                delete task;
            }
        }
    }

    int main()
    {
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond, NULL);

        pthread_t tid;
        pthread_create(&tid, NULL, work_thread, NULL);

        while(1)
        {
            int i;
            // 阻塞的,等待任务
            cin >> i;

            // 构造任务结构体
            task_t* task = new task_t;
            task->task = i;

            // 把任务丢到任务列表中
            pthread_mutex_lock(&mutex);
            tasks.push_back(task);
            pthread_mutex_unlock(&mutex);

            // 唤醒条件变量
            pthread_cond_signal(&cond);
        }
    }

    //运用析构函数

    #ifndef __AUTO_LOCK_H__
    #define __AUTO_LOCK_H__

    #include <pthread.h>

    class auto_lock
    {
    public:
        auto_lock(pthread_mutex_t& m);
        ~auto_lock();
    private:
        pthread_mutex_t& mutex;
    };

    #endif



    #include "auto_lock.h"

    auto_lock::auto_lock(pthread_mutex_t& m): mutex(m)
    {
        pthread_mutex_lock(&mutex);
    }

    auto_lock::~auto_lock()
    {
        pthread_mutex_unlock(&mutex);
    }



    #include <stdio.h>
    #include "auto_lock.h"

    pthread_mutex_t mutex;
    int result = 0;

    void* thread_func(void*ptr)
    {
        for(int i=0 ;i<100000; ++i)
        {
            auto_lock var1(mutex); // 重复加锁
            auto_lock var(mutex); // 在构造里自动加锁
            result++;
        }
    }

    int main()
    {
        // 变成递归锁   及循环锁  
        pthread_mutexattr_t attr;//设计循环锁属性
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 

        // 用递归属性去初始化这个锁
        pthread_mutex_init(&mutex, &attr);

        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, thread_func, NULL);
        pthread_create(&tid2, NULL, thread_func, NULL);

        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        printf("result is %dn", result);
    }

 

相对的解决方法:

  • 读写锁

  • #include

    pthread_rwlock_t mutex;
    int result;
    
    void* thread_func(void* ptr)
    {
        pthread_rwlock_rdlock(&mutex);
        // 只能对数据读
        result ++; // 写数据的行为是会导致数据不正确
        pthread_rwlock_unlock(&mutex);
    
        pthread_rwlock_wrlock(&mutex);
        // 可以对数据读写
        pthread_rwlock_unlock(&mutex);
    }
    
    int main()
    {
    
        pthread_rwlock_init(&mutex, NULL);
    
        pthread_t tid;
        pthread_create(&tid, NULL, thread_func, NULL);
    }
    

     

  • 循环锁

7.7.2.1 基本锁

类型:pthread_mutex_t
定义的变量一般在全局:pthread_mutex_t g_mutex;
在使用之前要初始化:pthread_mutex_init(&g_mutex, NULL);
访问敏感对象前加锁:pthread_mutex_lock(&g_mutex);
访问结束要解锁:pthread_mutex_unlock(&g_mutex);

一把所可以负责多个全局变量的安全问题,但是负责的范围越大,效率越低,代码相对容易写。负责全局变量的数量,被称之为锁的粒度。

死锁问题

  1. 忘了解锁会产生死锁

  2. 重复加锁会导致死锁

怎么解决死锁问题:

  1. 忘了解锁:程序员自己要注意

  2. 重复加锁:使用循环锁可以解决问题

7.7.2.2 循环锁

解决重复加锁导致死锁问题,循环锁的特点是,同一个线程进行多次加锁,不会阻塞。
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex); //
第二次加锁不会阻塞,但是它会给mutex增加一个计数。
pthread_mutex_unlock(&mutex) // 减少计数
pthread_mutex_unlock(&mutex);//减少到0的时候,真正解锁

怎么设置循环锁。

     pthread_mutexattr_t attr;

     // 设置成循环锁属性

     pthread_mutexattr_init(&attr);

     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

 

     // 此时mutex是一个循环锁

     pthread_mutex_init(&mutex, &attr);

 

//头文件
#ifndef __AUTO_LOCK_H__
#define __AUTO_LOCK_H__

#include <pthread.h>

class auto_lock
{
public:
auto_lock(pthread_mutex_t& m);
~auto_lock();
private:
pthread_mutex_t& mutex;
};

#endif

//头文件的实现

#include “auto_lock.h”

auto_lock::auto_lock(pthread_mutex_t& m): mutex(m)
{
pthread_mutex_lock(&mutex);
}

auto_lock::~auto_lock()
{
pthread_mutex_unlock(&mutex);
}

//主函数
#include <stdio.h>
#include "auto_lock.h"

pthread_mutex_t mutex;
int result = 0;

void* thread_func(void*ptr)
{
    for(int i=0 ;i<100000; ++i)
    {
        auto_lock var1(mutex); // 重复加锁
        auto_lock var(mutex); // 在构造里自动加锁
        result++;
    }
}

int main()
{
    // 变成递归锁
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 

    // 用递归属性去初始化这个锁
    pthread_mutex_init(&mutex, &attr);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func, NULL);
    pthread_create(&tid2, NULL, thread_func, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("result is %dn", result);
}

 

7.7.2.3 读共享写排他锁(读写锁)

共享锁/排他锁
定义锁:pthread_rwlock_t mutex;
初始化:pthread_rwlock_init(&mutex, NULL);
读锁定:pthread_rwlock_rdlock(&mutex);
写锁定:pthread_rwlock_wrlock(&mutex);
解锁:pthread_rwlock_unlock(&mutex);

7.7.2.4 总结

  1. 无论是什么锁,都会导致性能下降,所以能不用就尽量不用

  2. 锁能不能用于进程间同步?可以

 C++使用构造函数和析构函数自动加锁解锁

7.7.3 条件变量

条件变量是另外一种同步机制,它可以使线程在无竞争的等待条件发生。在之前讲到的线程场景里,子线程往往要等到队列有数据才运行,否则它应该休眠,以避免浪费CPU。但是如果用锁来实现这种机制的话,会非常麻烦。

定义:pthread_cond_t g_cond;
初始化:pthread_cond_init(&g_cond);
等待:pthread_cond_wait(&g_cond, &g_mutex);
唤醒:pthread_cond_signal(&g_cond);
pthread_cond_broadcast(&g_cond);
惊群

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

pthread_mutex_t mutex;
pthread_cond_t cond;

void* thread_func(void* ptr)
{
    sleep(1);

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);

    printf("wait okn");
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    // 发信号时,线程不是正在调用pthread_cond_wait,而是在执行sleep(1),所以signal发送之后,就消失了,不会保留
    // 按照刚才的说法,这个signal根本无效
    // 所以当一个线程发送多次的signal时,那么最多只有一次是有作用的
    pthread_cond_signal(&cond);

    pthread_join(tid, NULL);

}

 

7.7.3.1 条件变量的等待和唤醒

如果没有线程在等待条件,此时唤醒函数pthread_cond_signal不会唤醒任何的线程,也不会记录。

如果有多个线程在执行pthread_cond_wait,而此时有一个线程调用pthread_cond_signal,那么只会唤醒其中一个线程。

如果想唤醒所有线程,那么调用pthread_cond_broadcast,该函数可以唤醒等待该条件的所有线程。

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

// 假如有三个线程同时调用pthread_cond_wait,一个线程调用pthread_cond_signal
//
pthread_mutex_t mutex;
pthread_cond_t cond;

void* thread_func(void* ptr)
{

    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&cond, &mutex);
    pthread_mutex_unlock(&mutex);

    printf("wait okn");
}

int main()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, thread_func, NULL);
    pthread_create(&tid2, NULL, thread_func, NULL);
    pthread_create(&tid3, NULL, thread_func, NULL);

    sleep(1);
    // 唤醒一个线程
//    pthread_cond_signal(&cond);
//    唤醒所有正在等待的线程
    pthread_cond_broadcast(&cond);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

}

 

7.7.4 信号量

信号量类似条件变量,但是信号量可以保存信号数量。

  • 定义: sem_t sem;

  • 初始化:sem_init(&sem, 0, 0);
    初始化的第二个参数,如果是0表示同一进程内的多线程之间的信号量,如果是非0,那么该信号量可以使用在进程之间。第三个参数表示信号量的初始值。

  • 等待:sem_wait(&sem);
    sem_wait函数会导致该线程休眠,唤醒的条件是sem的值大于0。并且sem_wait调用结束后,会自动将sem值减1。

  • 唤醒:sem_post(&sem);
    sem_post只是简单的将sem值+1

    #include <stdio.h>
    #include <semaphore.h>
    #include <pthread.h>
    
    sem_t sem;
    
    void* thread_func(void* ptr)
    {
        sleep(1);
        sem_wait(&sem);
        printf("wait okn");
    }
    
    int main()
    {
        sem_init(&sem, 0, 0);
    
        pthread_t tid1, tid2, tid3;
        pthread_create(&tid1, NULL, thread_func, NULL);
        pthread_create(&tid2, NULL, thread_func, NULL);
        pthread_create(&tid3, NULL, thread_func, NULL);
    
        // 发送信号
        sem_post(&sem);
    
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        pthread_join(tid3, NULL);
    }
    

     

7.8 重入

如果函数操作了全局变量,这个函数就不是可重入的函数了。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
int result = 0;
void foo()
{
    // 因为这个函数操作了全局变量
    result ++;
}

void* thread_func(void* ptr)
{
#if 0
    int i;
    for(i=0; i<10000; ++i)
    {
        // 该函数是不可重入的函数
        // 用锁来保护它
        foo();
    }
#endif

    char p[] = "1 2 3 4 5 6 7 8 9 0";

    char* saveptr;
    char* sub = strtok_r(p, " ", &saveptr);
    while(sub)
    {
        usleep(1000); // 1毫秒        
        printf("%s, tid=%dn", sub, (int)pthread_self());
        sub = strtok_r(NULL, " ", &saveptr);
    }

}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func, NULL);
    pthread_create(&tid2, NULL, thread_func, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("result=%dn", result);
}

 

7.9 分离的线程

分离的线程不用pthread_join,也无法通过pthread_join来获取结果。因为它运行结束之后,它的PCB同时被释放了。

#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <inttypes.h>

// intptr_t 整数类型:char short int long (long long)
//     整数:8 16 32 64
//     有些机器的int是32位,有的机器是64位
//     void*指针类型都是按照机器的字长决定
//
//     intptr_t是一个整数,并且它总是和指针的字节数是一样的

void* thread_func(void* ptr)
{
    // 用的是地址本身,而不是地址指向的值
    printf("%dn", (int)(intptr_t)ptr);
    sleep(1);
}

int foo()
{
    char p[] = "hello world";
    int a = 100;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    pthread_t tid;
    pthread_create(&tid, &attr, thread_func, (void*)(intptr_t)a);

    // 该线程自生自灭
    // pthread_detach(tid);

    int ret = pthread_join(tid, NULL);
    printf("join error, ret=%d, errno=%d, EINVAL=%dn", ret, errno, EINVAL);
}

int main()
{
    foo();
    sleep(2);
}

 

7.10 线程私有数据

线程可以定义私有数据,私有数据只供该线程使用。
线程私有数据可以在该线程调用函数中访问,其他线程调用的函数中,不可访问。

// 定义线程私有数据的key,是在线程设置和使用私有数据之前创建

pthread_key_t key;

pthread_key_create(&key, 用来清理私有数据的函数指针);

 

// 设置私有数据,该函数被那个线程调用,那么就是设置该线程私有数据

pthread_set_specific(key, data);

void* data = pthread_get_specific(key);

 

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

pthread_key_t key;

// 可能被线程A调用
// 也可能线程B调用
void foo()
{
    char* p = (char*)pthread_getspecific(key);
    printf("%sn", p);
}

void my_malloc()
{
    // 去这个线程的内存池去申请内存
    void* mempool = pthread_getspecific(key);
  //  __my_malloc(mempool, ...);
}

void* thread_func(void* ptr)
{
    // setspecific,需要在线程中调用,当然也可以在主线程中调用
    // 为这个线程设置私有数据
    pthread_setspecific(key, ptr);

    foo();
    my_malloc();
    return NULL;
}

void free_func(void* ptr)
{
    printf("free calln");
    free(ptr);
}

int main()
{
    pthread_key_create(&key, free_func);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func, strdup("thread1"));
    pthread_create(&tid2, NULL, thread_func, strdup("thread2"));

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

}

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

// 线程本地变量,每个线程都有一份拷贝
thread_local int result = 0;

void foo()
{
    // 全局变量
    thread_local static int a = 0;
    a++;
    printf("%dn", a);
}



void* thread_func1(void* ptr)
{
    foo();
    foo();
    result = 100;
}

void* thread_func2(void* ptr)
{
    foo();
    foo();

    sleep(1);
//    printf("%dn", result); // 100
    printf("%dn", result); // thread_local时,这个值是0
}

int main()
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, thread_func1, NULL);
    pthread_create(&tid2, NULL, thread_func2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
}

 

7.11 线程取消

取消线程也结束线程,但是应该避免这种设计。

退出点函数:man pthreads搜索cancel关键字,找到这些退出点函数。

pthread_cancel在线程外部(其他线程)来退出另外一个线程A,当线程A调用了cancelpoint函数时,会退出。

如果希望调用cancelpoint函数不退出,应该设置当前的线程状态为:不理会线程退出(cancelability
disabled)
pthread_setcancelstate(…)

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

void* thread_func(void* ptr)
{
    // 因为这个线程没有cancel point
    while(1)
    {
        // 关闭cancel检测
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        sleep(10);

        // 打开cancel检测
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        // 检查cancel point
        pthread_testcancel(); 
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);

    // 让线程退出
    pthread_cancel(tid);

    // 等待线程退出
    pthread_join(tid, NULL);
}