学习POSIX和C++多线程开发

《Hands-On System Programming with C++》读书笔记之十二

理解POSIX线程

线程与进程的主要区别在于

  • 线程存在于进程之内
  • 同一进程内的线程共享内存空间,进程之间不共享(除非特意开辟共享内存)

线程同样由操作系统调度,面临竞争与死锁等问题。

POSIX线程基础

一个简单的例子:

#include 
#include 

void *mythread(void *ptr)
{
   
    std::cout << "Hello World\n";
    return nullptr;
}

int main()
{
   
    pthread_t thread1;
    pthread_t thread2;

    pthread_create(&thread1, nullptr, mythread, nullptr);
    pthread_create(&thread2, nullptr, mythread, nullptr);
    pthread_join(thread1, nullptr);
    pthread_join(thread2, nullptr);
}

g++ scratchpad.cpp -lpthread命令编译,结果打印

thread id: 140609000044288
thread id: 140608991651584

用于创建线程的pthread_create()原型是

int pthread_create(
	pthread_t *thread,
	const pthread_attr_t *attr,
	void *(*start_routine)(void*),
	void *arg
);

用于等待线程执行结束的*pthread_join()*函数原型是

int pthread_join(
	pthread_t thread,
	void **value_ptr
);

第二个参数是返回值。

上面的例子面临几个问题:

  • 类型安全:参数、返回值都用的是void*传递,没有任何类型信息;
  • 竞争:线程可能同时向stdout输出,引起竞争;
  • 没有输入/输出。

POSIX规定线程要有ID用于识别,可以用*pthread_self()*查看。

一个带输入/输出的例子如下:

#include 
#include 

void *mythread(void *ptr)
{
   
    (*reinterpret_cast<int*>(ptr))++;
    return ptr;
}

int main()
{
   
    int in_value = 42;
    void *out_value = nullptr;
    pthread_t thread1;
    pthread_t thread2;

    pthread_create(&thread1, nullptr, mythread, &in_value);
    pthread_create(&thread2, nullptr, mythread, &in_value);
    pthread_join(thread1, &out_value);
    pthread_join(thread2, &out_value);
    std::cout << "value: " << 
        *reinterpret_cast<int*>(out_value) << '\n'; // 44
}

这里两个线程操作的是同一个整型,同样由竞争的问题。

Yielding

不及时退出的线程可能会消耗过多的CPU时间。

#include 
#include 

void *mythread(void *ptr)
{
   
    while(true)
    {
   
        std::clog << static_cast<char*>(ptr) << '\n';
        pthread_yield();
    }
}

int main()
{
   
    char name1[9] = "thread 1";
    char name2[9] = "thread 2";
    pthread_t thread1;
    pthread_t thread2;
    pthread_create(&thread1, nullptr, mythread, name1);
    pthread_create(&thread2, nullptr, mythread, name2);
    pthread_join(thread1, nullptr);
    pthread_join(thread2, nullptr);
}

上面的程序会不停地打印thread 1thread 2,但两种打印的交替频率很快。这是pthread_yield()的作用,它允许当前线程释放对CPU的占用。如果不调用它,会发现thread 1重复打印很久才开始打印thread 2,并再次重复打印多次。
需要注意的是操作系统本身能够有效地调度线程,除非有很明确的目的,否则过度使用yileding反而会降低性能。而且这个函数并非在所有系统中都可用。
可以使用*sleep()*使线程进入休眠,用于调整性能和优化电池使用。

同步

给出一个发生竞争的例子:

#include 
#include 
#include 

int count = 0;

void *mythread(void *ptr)
{
   
    count++;
}

int main()
{
   
    while(true)
    {
   
        count = 0;
        for(auto i =0; i < 1000; i++)
        {
   
            std::array<pthread_t, 8> threads;
            for (auto &t : threads) {
   
                pthread_create(&t, nullptr, mythread, nullptr);
            }
            for (auto &t : threads)
            {
   
                pthread_join(t, nullptr);
            }
        }
        std::cout << "count: " << count << '\n';
    }
}

上面的打印结果理论上应该是8000,但很多打印结果出现了7998、7999等,说明出现了对count全局变量的竞争。

用互斥量mutex解决:

#include 
#include 
#include 

int count = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *mythread(void *ptr)
{
   
    pthread_mutex_lock(&lock);
    count++;
    pthread_mutex_unlock(&lock);
}

int main()
{
   
    while(true)
    {
   
        count = 0;
        for(auto i =0; i < 1000; i++)
        {
   
            std::array<pthread_t, 8> threads;
            for (auto &t : threads) {
   
                pthread_create(&t, nullptr, mythread, nullptr);
            }
            for (auto &t : threads)
            {
   
                pthread_join(t, nullptr);
            }
        }
        std::cout << "count: " << cou

你可能感兴趣的:(读书笔记,c++,多线程,UNIX,LINUX,编程)