Linux多线程编程

线程

线程的概念:轻量级的进程,一个进程内部可以有多个线程,默认情况下一个进程只有一个线程。
线程是最小的执行单位,进程是最小的系统资源分配单位。

内核实现都是通过clone函数实现的。
线程也有自己的PCB。

线程非共享资源:1. 线程id;2. 处理器现场和栈指针(内核栈);3. 独立的栈空间(用户空间栈);3. 信号屏蔽字;4. 调度优先级。
线程共享资源:1. text;2. data;3. rodata, 4. bss;4. heap;5. errno变量。

线程的优点:1. 提高并发性; 2. 占用资源小;3. 通信方便。
线程的缺点:1. 调试困难;2. 库函数,不稳定;3. 对信号支持不好。
缺点可以克服,优点很突出。

线程的操作函数

pthread_t pthread_self(void);
获取线程id。 线程id是在进程地址空间内部,用来标识线程身份的id号。
返回值:本线程id。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
创建线程。
参1:传出参数,表新创建的子线程 id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。
参4:参3的参数。没有的话,传NULL
返回值:成功为0;失败:errno。

void pthread_exit(void *retval);
retval:退出值。 无退出值时,NULL
exit();	退出当前进程。
return: 返回到调用者那里去。
pthread_exit(): 退出当前线程。

int pthread_join(pthread_t thread, void**retval);
阻塞 回收线程。
thread: 待回收的线程id。
retval:传出参数。 回收的那个线程的退出值。线程异常退出值为 -1。
返回值:成功:0;失败:errno。

int pthread_cancel(pthread_t thread);
杀死一个线程。  需要到达取消点(保存点)。如果,子线程没有到达取消点, 那么 pthread_cancel 无效。我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();成功被 pthread_cancel() 杀死的线程,使用pthead_join 回收。
thread: 待杀死的线程id,
返回值:成功:0;失败:errno。

int pthread_detach(pthread_t thread);
设置线程分离,分离后的线程会自动回收,不能用pthread_join再次回收。
thread:待分离分线程id。
返回值:成功为0;失败为errno。

int pthread_equal(pthread_t t1, pthread_t t2);
比较两个线程是否相同,如果一样返回一个非零值,不一样返回0。

线程属性控制
pthread_attr_t attr;  	创建一个线程属性结构体变量
int pthread_attr_init(pthread_attr_t*attr);                                       	--初始化线程属性
pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);
--设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL);
--借助修改后的设置线程属性,创建为分离态的新线程
int pthread_attr_destroy(pthread_attr_t*attr);                               		--销毁线程属性
线程和进程相关函数
线程控制原语					进程控制原语
pthread_create()				fork();
pthread_self()					getpid();
pthread_exit()					exit(); 		/ return 
pthread_join()					wait()/waitpid()
pthread_cancel()				kill()
pthread_detach()
//01_pthread_create.c

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

void* thr(void *arg)
{
    printf("I am thread! pid = %d, tid = %lu\n", getpid(), pthread_self());
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);
    printf("I am a main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
    sleep(1);

    return 0;
}
//02_pthread_exit.c

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

void* thr(void *arg)
{
    printf("I am a thread, tid = %lu\n", pthread_self());
    //return NULL;
    pthread_exit(NULL);
    //exit(1);
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);
    printf("I am main thread, tid = %lu\n", pthread_self());
    
    sleep(5);
    printf("I will out\n");
    pthread_exit(NULL);

    return 0;
}
//03_pthread_return.c

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

void* thr(void *arg)
{
    printf("I am a thread, tid = %lu\n", pthread_self());
    sleep(5);
    printf("I am a thread, tid = %lu\n", pthread_self());
    pthread_exit((void*)100);
    //return (void*)100;
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);
    void *ret;
    pthread_join(tid, &ret);            //线程回收

    printf("ret exit with %d\n", (int)ret);

    return 0;
}
//04_pthread_cancel.c

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

void* thr(void *arg)
{
    while (1)
    {
        printf("I am a thread, tid = %lu\n", pthread_self());
        sleep(1);
    }
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);

    sleep(3);
    if (pthread_cancel(tid) != 0)
    {
        perror("kill thread error!");
        return -1;
    }

    void *ret;
    pthread_join(tid, &ret);
    printf("thread exit with %d\n", (int)ret);

    return 0;
}
//05_pthread_detach.c

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

void* thr(void *arg)
{
    printf("I am a thread, self tid = %lu\n", pthread_self());
    sleep(3);
    printf("I am a thread, self tid = %lu\n", pthread_self());
    return NULL;
}

int main(int argc, char const *argv[])
{
    int ret = -1;
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);
    ret = pthread_detach(tid);            //线程分离
    if (ret != 0)   
    {
        perror("pthread_detach error!");
    }
    //sleep(5);

    ret = pthread_join(tid, NULL);
    if (ret != 0)
    {
        printf("pthread_join error, err:%d\n", ret);
    }
    return 0;
}
//06_pthread_var.c

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

int var = 100;

void* thr(void *arg)
{
    sleep(1);
    printf("I am a thread, self tid = %lu, var = %d\n", pthread_self(), var);
    sleep(2);
    var = 1001;
    printf("I am a thread, self tid = %lu, var = %d\n", pthread_self(), var);
    return NULL;
}

int main(int argc, char const *argv[])
{
    int ret = -1;
    pthread_t tid;
    pthread_create(&tid, NULL, thr, NULL);

    ret = pthread_detach(tid);            //线程分离
    if (ret != 0)   
    {
        perror("pthread_detach error!");
    }
    printf("I am a main thread, self tid = %lu, var = %d\n", pthread_self(), var);
    var = 1003;
    sleep(5);
    printf("I am a main thread, self tid = %lu, var = %d\n", pthread_self(), var);
    
    ret = pthread_join(tid, NULL);
    if (ret != 0)
    {
        printf("pthread_join error, err:%d\n", ret);
    }
    return 0;
}
//07_npthread.c

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

void* thr(void *arg)
{
    int num = (int)arg;
    printf("I am %d thread, self = %lu\n", num, pthread_self());
    return (void *)(100 + num);
}

int main(int argc, char const *argv[])
{
    pthread_t tid[5];
    int i = 0;
    for (; i < 5; ++i)
    {
        pthread_create(&tid[i], NULL, thr, (void*)i);
    }

    for (i = 0; i < 5; ++i)
    {
        void *ret;
        pthread_join(tid[i], &ret);
        printf("i = %d, ret = %d\n", i, (int)ret);
    }
    return 0;
}
//08_pthread_attr.c

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

void* thr(void *arg)
{
    printf("I am a thread\n");
    return NULL;
}

int main(int argc, char const *argv[])
{
    pthread_attr_t attr;
    pthread_attr_init(&attr);               //初始化属性

    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);        //设置属性隔离

    pthread_t tid;
    pthread_create(&tid, &attr, thr, NULL);

    sleep(3);
    int ret = pthread_join(tid, NULL);
    if (ret > 0)
    {
        printf("join err:%d, %s\n", ret, strerror(ret));
    }

    pthread_attr_destroy(&attr);            //销毁属性
    return 0;
}
//09_pthread_print.c

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

int sum = 0;

void* thr1(void *arg)
{
    while (1)
    {
        printf("hello ");
        sleep(rand() % 3);
        printf("world\n");
        sleep(rand() % 3);
    }
}

void* thr2(void *arg)
{
    while (1)
    {
        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        sleep(rand() % 3);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid[2];
    pthread_create(&tid[0], NULL, thr1, NULL);
    pthread_create(&tid[1], NULL, thr2, NULL);

    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    return 0;
}

互斥锁

线程访问一个共享资源,需要协调步骤!
线程同步:协调步调,对公共数据按顺序执行。防止数据混乱,产生与时间有关的错误。

数据混乱的原因:1.资源共享(独享资源则不会);2.调度随机(意味着数据访问会出现竞争);3.线程间缺乏必要的同步机制。
前两点不能改变,欲提高效率,传递数据,资源必须共享,只要共享资源,就一定会出现竞争。只能解决第3个原因带来的数据混乱问题。
解决同步的问题:加锁!使多个线程在访问共享资源的时候,出现互斥。

互斥量mutex
每个线程在岁资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争大。

使用mutex的一般步骤:

1.pthread_mutex_t lock;		//创建锁
//pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。变量mutex只有两种取值:0,1
2.pthread_mutex_init		//初始化	1
3.pthread_mutex_lock		//加锁		1-- -> 0
4.访问共享数据
5.pthread_mutex_unlock		///解锁		0++ -> 1
6.pthread_mutex_destroy		//销毁锁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
						const pthread_mutexattr_t *restrict attr);
restrict关键字:用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。
pthread_mutex_t mutex;
1. pthread_mutex_init(&mutex, NULL);   			///动态初始化。
2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;	//静态初始化。


注意事项:
1.尽量保证锁的粒度,越小越好。(访问共享数据前,加锁。访问结束【立即】解锁。)
2.互斥锁,本质是结构体。我们可以看成整数。初值为1。(pthread_mutex_init函数调用成功)
3.加锁。--操作,阻塞线程。
4.解锁。++操作,唤醒阻塞在锁上的线程。
5.try锁,尝试加锁,成功加锁成功(--);失败直接返回错误号(如EBUSY),不阻塞。
//mutex

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

pthread_mutex_t mutex;          //定义一把互斥锁

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

void* thr(void* arg)
{
    srand(time(nullptr));
    while (true)
    {
        if (pthread_mutex_lock(&mutex))             //加锁,可以想象成锁--(1-- -> 0)
        {
            sys_err("pthread mutex lock error!\n");
        }

        printf("hello ");       
        sleep(rand() % 3);      //模拟长时间操作共享资源,导致CPU易主,产生与实践有关的错误
        printf("world!\n");

        if (pthread_mutex_unlock(&mutex))           //解锁,可以想象成锁++(0++ -> 1)
        {
            sys_err("pthread mutex unlock error!\n");
        }

        sleep(rand() % 3);
    }
    pthread_exit(nullptr);
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    srand(time(nullptr));
    if (pthread_mutex_init(&mutex, nullptr))        //初始化互斥锁, 可以认为,锁的值是1
    {
        sys_err("pthread mutex init failed!\n");
    }

    if (pthread_create(&tid, nullptr, thr, nullptr))
    {
        sys_err("pthread create error!\n");
    }

    //主线程
    while (true)
    {
        if (pthread_mutex_lock(&mutex))             //加锁,可以想象成锁--(1-- -> 0)
        {
            sys_err("pthread mutex lock error!\n");
        }
        
        printf("HELLO ");       
        sleep(rand() % 3);      //模拟长时间操作共享资源,导致CPU易主,产生与实践有关的错误
        printf("WORLD!\n");
        
        if (pthread_mutex_unlock(&mutex))           //解锁,可以想象成锁++(0++ -> 1)
        {
            sys_err("pthread mutex unlock error!\n");
        }

        sleep(rand() % 3);
    }
    
    if (pthread_join(tid, nullptr))
    {
        sys_err("pthread join error!");
    }

    if (pthread_mutex_destroy(&mutex))
    {
        sys_err("pthread mutex destroy error!\n");
    }
    

    return 0;
}

读写锁

读写锁:锁只有一把,以读方式给数据加锁–读锁;以写方式给数据加锁–写锁。读共享、写独占。写锁优先级高。

读写锁也叫共享-独占锁。当读写锁以“读模式加锁”时,它是以共享模式锁住的;当它以“写模式加锁”时,它是以独占模式锁住的。写独占、读共享。
读写锁非常适合于对数据结构读的次数远大于写的情况。

1.读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会阻塞;
2.读写锁是“读模式加锁”时,如果线程以“读模式加锁”会成功;如果线程以“写模式加锁”会阻塞。
3.读写锁是“读模式加锁”时,既有试图以“写模式加锁”的线程,也有试图以“读模式加锁”的线程,那么读写锁会阻塞随后的“读模式加锁”请求,优先满足“写模式加锁”请求。读锁、写锁并行阻塞,写锁优先级高。

pthread_rwlock_t 类型	用于定义一个读写锁变量
pthread_rwlock_t  rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_rdlock(&rwlock);		try
pthread_rwlock_wrlock(&rwlock);		try
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_destroy(&rwlock);

以上函数都是成功返回0,失败返回错误号。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

int counter = 0; //共享资源
pthread_rwlock_t rwlock;

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

//写线程的回调函数
void *th_write(void *arg)
{
    int t;
    long i = reinterpret_cast<long>(arg);
    while (true)
    {
        //写线程期间,独占
        if (pthread_rwlock_wrlock(&rwlock))
        {
            sys_err("pthread rwlock wrlock error!\n");
        }

        t = counter; //保存写之前的值
        sleep(1);    //模拟业务,让读线程获取CPU,但是读线程因为锁被锁住而堵塞
        printf("=======write %ld: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);

        if (pthread_rwlock_unlock(&rwlock))
        {
            sys_err("pthread relock unlock error!\n");
        }

        sleep(5); //让其它线程获取锁,防止本线程回到while又重新上锁,导致其它线程很少有机会获取到锁
    }

    pthread_exit(nullptr);
}

//读线程回调函数
void *th_read(void *arg)
{
    long i = reinterpret_cast<long>(arg);
    while (true)
    {
        //读线程期间,读时共享
        if (pthread_rwlock_wrlock(&rwlock))
        {
            sys_err("pthread rwlock wrlock error!\n");
        }

        printf("=======read %d: %lu: counter=%d\n", i, pthread_self(), counter);

        if (pthread_rwlock_unlock(&rwlock))
        {
            sys_err("pthread relock unlock error!\n");
        }

        sleep(1); //让其它线程获取锁,防止本线程回到while又重新上锁,导致其它线程很少有机会获取到锁
    }

    pthread_exit(nullptr);
}

int main(int argc, char const *argv[])
{
    pthread_t tid[8];
    if (pthread_rwlock_init(&rwlock, nullptr))
    {
        sys_err("pthread rwlock init error!\n");
    }

    //创建三个写线程
    for (int i = 0; i < 3; ++i)
    {
        if (pthread_create(&tid[i], nullptr, th_write, reinterpret_cast<void *>(i)))
        {
            sys_err("pthread create error!\n");
        }
    }

    //创建五个读线程
    for (int i = 2; i < 8; ++i)
    {
        if (pthread_create(&tid[i], nullptr, th_read, reinterpret_cast<void *>(i)))
        {
            sys_err("pthread create error!\n");
        }
    }

    //回收子线程资源
    for (int i = 0; i < 8; ++i)
    {
        if (pthread_join(tid[i], nullptr))
        {
            sys_err("pthread join error!\n");
        }
    }

    //释放读写锁
    if (pthread_rwlock_destroy(&rwlock))
    {
        sys_err("pthread rwlock unlock error!\n");
    }

    return 0;
}

死锁现象

使用锁不恰当造成的现象。

  1. 对一个锁反复lock。
  2. 两个线程,各自持有一把锁,请求另一把。
    死锁现象

条件变量

本身不是锁,但是通常结合锁来使用。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //静态初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); //动态初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); //阻塞等待条件
作用:
    1. 阻塞等待条件变量满足;
    2. 解锁已经加锁成功的信号量(相当于pthread_mutex_unlock(&mutex))。
12两步是一个原子操作。
    3. 当条件满足,函数返回时,解除阻塞并重新申请获取互斥锁。重新加锁信号量(相当于pthread_mutex_lock(&mutex))。
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒阻塞在条件变量上的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒阻塞在条件边上的(至少)一个线程
#include <iostream>
#include <string>
#include <memory>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

using namespace std;

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //定义互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; //定义一个条件变量

void sys_err(string str)
{
    cout << str << endl;
    pthread_exit(nullptr);
}

class msg
{
public:
    int num;
    shared_ptr<msg> next;
};

shared_ptr<msg> head = nullptr; //头结点

void *producer(void *arg)
{
    while (true)
    {
        //生产一个数据
        shared_ptr<msg> mp(new msg()); //生产的数据节点
        mp->num = rand() % 1000 + 1;
        cout << "--produce:" << mp->num << endl;

        //加锁 互斥量
        if (pthread_mutex_lock(&lock))
            sys_err("pthread mutex lock error!");

        //写共享区域
        mp->next = head;
        head = mp;

        //解锁 互斥量
        if (pthread_mutex_unlock(&lock))
            sys_err("pthread mutex unlock error!");

        //唤醒阻塞在条件变量has_data上的线程
        if (pthread_cond_signal(&has_data))
            sys_err("pthread cond signal error!");

        //让消费者拿到锁
        sleep(rand() % 3);
    }

    pthread_exit(nullptr);
}

void *consumer(void *arg)
{
    while (true)
    {
        shared_ptr<msg> mp_c(new msg());
        //加锁 互斥量
        if (pthread_mutex_lock(&lock))
            sys_err("pthread mutex lock error!");

        //阻塞等待条件变量,解锁
        if (head == nullptr)
        {
            if (pthread_cond_wait(&has_data, &lock))
                sys_err("pthread cond wait error!"); 
        }
        
        mp_c = head;
        head = mp_c->next;

        //解锁 互斥量
        if (pthread_mutex_unlock(&lock))
            sys_err("pthread mutex unlock error!");

        cout << "----consumer:" << mp_c->num << endl; 

        sleep(rand() % 3);
    }
}


int main(int argc, char const *argv[])
{
    srand(time(nullptr));
    pthread_t tid1, tid2;

    if (pthread_create(&tid1, nullptr, producer, nullptr))
        sys_err("pthread create producer error!");
    
    if (pthread_create(&tid1, nullptr, consumer, nullptr))
        sys_err("pthread create producer error!");

    if (pthread_join(tid1, nullptr))
        sys_err("pthread join producer error!");

    if (pthread_join(tid2, nullptr))
        sys_err("pthread join consumer error!");

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值